package CardGame;

import Message.BeanConstant;
import Message.ServiceConstant;
import DataManager.DbManager;
import DataManager.MySQLCommonDataProvider;
import DataManager.VCoreUserDataProvider;
import Message.SerializerHelper;
import Service.CardConstant;
import Service.LeaveUserObj;
import Service.ServerService;
import Service.StringConstant;
import Service.UserObj;
import Utility.Memcache;
import Message.Message;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.mina.core.session.IoSession;
import Message.Base64;
import Message.SerializerHelper;

//import com.vivoo.VCoreUserDataProvider;
/**
 *
 * @author Nhan Nguyen
 */
public class TienLenService {

    private final int DEFAULT_FLAG_CHEAT = 0;
    private final int WIN_TYPE_NORMAL = 0;
    private final int WIN_TYPE_3B = 2;
//    private final int WIN_AN_TRANG = 1;
//    private final int WIN_TOI_TRANG = 2;
    private final int GAME_END_USER_QUIT = 0;
    private final int GAME_END_OUT_OF_CARD = 1;
    private final int GAME_END_3B = 2;    // TOI NHAT 3 BICH
    private final float THREE_PLAYER_RATIO = (float) 1 / 4;   // TI LE THUA NHI KHI DANH 3 TAY
    private final float FOUR_PLAYER_RATIO = (float) 1 / 2;    // TI LE THUA BA KHI DANH 4 TAY
    private TienLenLogic gameLogic = null;
    public static Logger log = LoggerFactory.getLogger(TienLenService.class.getName());
    public static Logger logChat = LoggerFactory.getLogger("ChatLog");
    public static Logger logGame = LoggerFactory.getLogger("GameLog");

    public TienLenService() {
        log.info("TienLenService initialized");
        gameLogic = new TienLenLogic();
    }

    public void processCommand(IoSession session, int userID, String userName, Message clientMsg) {
//        if (userID == 0) {
//            log.info("Connection is not registered");
//            session.close(true);
//            return;
//        }
        int actionCommand = clientMsg.getActionCommand();
        switch (actionCommand) {
            case BeanConstant.GET_LIST_ROOM:
                sendUserRooms(session);
                break;
            case BeanConstant.BT_CLIENT_BEAN:
                sendUserTables(session, userName, clientMsg);
                break;
//            case BeanConstant.BT_WATCH_ACCOUNT:
//                sendAccountInfo(session, userID);
//                break;
            case BeanConstant.BT_REQUEST_JOIN_TABLE_BEAN:
                joinTable(session, userName, userID, clientMsg);
                break;
            case BeanConstant.BT_MOVE_BEAN:
                processMove(session, userName, clientMsg);
                break;
            case BeanConstant.BT_START_BEAN:
                startGame(session, userName, clientMsg);
                break;

            case BeanConstant.BT_CHAT_BEAN: {
                sendChatMessage(session, userName, clientMsg);
                break;
            }
            case BeanConstant.BT_REQUEST_SET_BET_MONEY:
                setBetMoney(session, userID, clientMsg);
                break;

            case BeanConstant.BT_LEAVE_TABLE_BEAN:
                String tableID = SerializerHelper.readString(clientMsg);
                String leaveUser = SerializerHelper.readString(clientMsg);
                if (userName.equals(leaveUser) && (tableID != null)) {
                    leaveTable(session, userName, tableID, true);
                }
                break;

            case BeanConstant.KICK_USER_OF_OUT_TABLE:
                kickOutOfTable(session, userName, clientMsg);
                break;

            case BeanConstant.BT_IGNORE_BEAN:
                int tableType = SerializerHelper.readInt(clientMsg);
                tableID = SerializerHelper.readString(clientMsg);
                String userBypass = SerializerHelper.readString(clientMsg);

                bypassMove(session, tableType, tableID, userName, userBypass);
                break;
            case BeanConstant.BT_REQUEST_FIND_AND_JOIN_TABLE_BEAN:
                autoFindRoom(session, userID);
                break;
            case BeanConstant.GET_AVAILABLE_PLAYERS:
                sendUserAvailabe(session, userID, clientMsg);
                break;
            case BeanConstant.INVITE_PLAYER:
                inviteUser(session, userName, clientMsg);
                break;

            case BeanConstant.LOCK_TABLE:
                lockTable(session, userName, userID, clientMsg);
                break;
        }
    }

    private void sendUserRooms(IoSession session) {
        try {
            Message msg = new Message(BeanConstant.RESPONSE_GET_LIST_ROOM, ServiceConstant.GAME_CARD_SERVICE);
            //Collection roomList = RoomManager.rooms.values();
            Collection roomList = RoomManager.getInstance().getRoomList();
            //gửi về tổng số phòng
            SerializerHelper.writeInt(roomList.size(), msg);
            int k = 0;
            for (Iterator room = roomList.iterator(); room.hasNext();) {
                String roomCode = (String) room.next();
//                log.info(roomCode);
                GameRoom gr = RoomManager.getInstance().getRoomProperties(roomCode);
                ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
                //String[] tableList = RoomManager.getInstance().getTableList();
                int unemptyTable = 0;           // số bàn đã có người
                //int numOfTable = tableList.length;
                int numOfTable = arr.size();
                // tổng số bàn trong phòng
                //String strRoom = String.format("TL%04d", k);
                SerializerHelper.writeString(roomCode, msg);//room id
                for (int i = 0; i < arr.size(); i++) {
                    GameTable gt = (GameTable) arr.get(i);
                    if (gt.getNumberOfPlayers() > 0) {
                        unemptyTable++;
                    }
                }
                SerializerHelper.writeInt(gr.type, msg);    //kiểu bàn
                SerializerHelper.writeInt(unemptyTable, msg);
                SerializerHelper.writeInt(numOfTable, msg);
                k++;
            }
            session.write(msg);
        } catch (Exception ex) {
            log.error("sendUserRooms", ex);
//            ex.printStackTrace();
        }
    }

    private void sendUserTables(IoSession session, String userName, Message clientMsg) {
        String roomId = SerializerHelper.readString(clientMsg);
//        log.info("room id: " + roomId);
        try {
            Message msg = new Message(BeanConstant.BT_TABLE_LIST_BEAN, ServiceConstant.SYSTEM_SERVICE);
            //ArrayList tableList = RoomManager.rooms.get(roomId);
            //ArrayList tableList = RoomManager.getInstance().getTableList(roomId);
//            String[] tableList = RoomManager.getInstance().getTableList();
//            int numOfTable = tableList.length;
            ArrayList tableList = RoomManager.getInstance().getTableList(roomId);
            SerializerHelper.writeInt(tableList.size(), msg);
            for (int i = 0; i < tableList.size(); i++) {
                GameTable gt = (GameTable) tableList.get(i);
                SerializerHelper.writeString(gt.tableID, msg);
                int tableStatus = gt.getNumberOfPlayers();
                if (tableStatus == 0) {
                    gt.isPlaying = false;
                }
                if (gt.isPlaying) {
                    tableStatus = CardConstant.TABLE_PLAYING_STATUS;
                }
                if (gt.password != null) {
                    tableStatus = CardConstant.TABLE_LOCKING_STATUS;
                }
                SerializerHelper.writeInt(tableStatus, msg);
                SerializerHelper.writeLong(gt.betMoney, msg);
                SerializerHelper.writeInt(gt.getNumberOfPlayers(), msg);
            }
            session.write(msg);
        } catch (Exception ex) {
            log.error("sendUserTables", ex);
//            ex.printStackTrace();
        }
    }

//    private void sendAccountInfo(IoSession session, int userID) {
//        try {
//            VCoreUserDataProvider dataManager = new VCoreUserDataProvider();
//            Message msg = new Message(BeanConstant.BT_WATCH_ACCOUNT, ServiceConstant.GAME_CARD_SERVICE);
//            SerializerHelper.writeString(joinUser, msg);
//            SerializerHelper.writeLong(dataManager.retrieveAccountBalance(userID), msg);
//            session.write(msg);
//            dataManager.closeConnection();
//        } catch (Exception ex) {
//            ex.printStackTrace();
//        }
//    }
    private void joinTable(IoSession session, String userName, int userID, Message clientMsg) {
        Message msgResponse = null;

        int roomType = SerializerHelper.readInt(clientMsg);
        String tableID = SerializerHelper.readString(clientMsg);

        String userPW = null;
        if (clientMsg.isDataAvailable()) {
            userPW = SerializerHelper.readString(clientMsg);
        }
//        String joinUser = (String) session.getAttribute("user");
        logGame.info("TableID=" + tableID + ", room type=" + roomType);

        byte result = 0;

        // cancel if user currently joined other table
        String currentTableID = (String) session.getAttribute("table");
        if (currentTableID != null) {
            logGame.error("[{}] currently joined multiple tables ({})", userName, currentTableID);
            result = CardConstant.JOIN_ERROR_BLOCK_TABLE;
        }

//        if ((!userName.equals(joinUser))) {
//            logGame.error("Invalid username ({}<>{})", joinUser, userName);
//            result = CardConstant.JOIN_ERROR_BLOCK_TABLE;
//        }

        if (result != 0) {
//            logGame.error("Force to leave first");
            leaveTable(session, userName, currentTableID, false);
            //return;
        }

        try {
            VCoreUserDataProvider dataManager = new VCoreUserDataProvider();
            MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();
            msgResponse = new Message(BeanConstant.BT_RESPOND_JOIN_TABLE_BEAN, ServiceConstant.SYSTEM_SERVICE);

            result = CardConstant.JOIN_ERROR_BLOCK_TABLE;

            //if (RoomManager.tables.containsKey(tableID)) {
            currentTableID = (String) session.getAttribute("table");
            if (RoomManager.getInstance().isTableExist(tableID)
                    && (currentTableID == null)) {
                result = CardConstant.JOIN_OK;
            }

//            if (RoomManager.getInstance().isTableExist(tableID)) {
            GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableID);
            if (result == CardConstant.JOIN_OK) {
                if (gt != null) {
                    synchronized (gt) {
                        boolean isEnoughPlayer = (gt.getNumberOfPlayers() < gt.getMaxPlayers());
//                        boolean isPlaying = gt.isPlaying;
                        boolean isEnoughMoney = commonDataManager.retrieveAccountBalance(userID) >= gt.betMoney;
                        boolean isPasswordOK = (gt.password == null || gt.password.equals(userPW));
                        //boolean joinOK = (gt.getNumberOfPlayers() < gt.getMaxPlayers()) &&
                        //  (!gt.isPlaying)&&(dataManager.retrieveAccountBalance(user)>=gt.betMoney);
                        //boolean joinOK = (numPlayer) && (!isPlaying) && (balancePlayer);
                        boolean joinOK = (isEnoughPlayer) && (isEnoughMoney) && (isPasswordOK);
                        if (!joinOK) {
                            if (!isEnoughPlayer) {
                                result = CardConstant.JOIN_ERROR_BLOCK_TABLE;
                            } else if (!isEnoughMoney) {
                                result = CardConstant.JOIN_ERROR_NOT_ENOUGH_MONEY;
                            } else {
                                if (userPW == null || userPW.length() == 0) {
                                    result = CardConstant.JOIN_ERROR_PASSWORD_REQUIRED;
                                } else {
                                    result = CardConstant.JOIN_ERROR_WRONG_PASSWORD;
                                }
                            }
                            SerializerHelper.writeByte(result, msgResponse);
                            SerializerHelper.writeString(gt.tableID, msgResponse); // table ID
                            SerializerHelper.writeLong(0, msgResponse); // betMoney
                            SerializerHelper.writeInt(0, msgResponse); // No of user
                            SerializerHelper.writeBoolean(false, msgResponse); // is playing
                            session.write(msgResponse);
                            logGame.error("[{}] Failed to joined ({})", userName, result);
                            return;
                        } else {
                            result = 1;
                            if (gt.playerList.isEmpty()) {
                                gt.isFirstDeal = true;
                                gt.isPlaying = false;
                            }
                            if (!gt.playerList.contains(userName)) {
                                gt.playerList.add(userName);
                                gt.isPlayerChanged = true;
                            }
                            if (!gt.isPlaying) {            // hanv add
                                gt.clearJoinedPlayerList();    // reset ready list
                            }
                            session.setAttribute("table", tableID);
                        }

                        log.info("Users=" + gt.playerList.size() + ", joined users=" + gt.getNumberOfJoinedPlayers());

                        SerializerHelper.writeByte(result, msgResponse);
                        SerializerHelper.writeString(gt.tableID, msgResponse);
                        SerializerHelper.writeLong(gt.betMoney, msgResponse);
                        SerializerHelper.writeInt(gt.getNumberOfPlayers(), msgResponse);
                        SerializerHelper.writeBoolean(gt.isPlaying, msgResponse);

                        //                        for (int i = gt.getNumberOfPlayers()-1; i>=0; i--) {
                        int i = 0;
                        while (i < gt.getNumberOfPlayers()) {
                            String user = (String) gt.playerList.get(i);
                            SerializerHelper.writeString(user, msgResponse);

                            //                        int playerID = VCoreUserDataProvider.getInstance().GetUserIdByUsername(user);
                            //                        UserObj userinfo = dataManager.getAccountInfo(playerID);
                            IoSession ses = PIServer.VMSProtocolHandler.getSessionByUserName(user);
                            if (ses == null) {
                                // neu ko tim thay nguoi choi do mat ket noi --> kick
                                if (!PIServer.VMSProtocolHandler.leaveUserQueue.containsKey(user)) {
                                    gt.playerList.remove(i);
                                    continue;
                                }
                            }
//                            int playerID = (Integer) ses.getAttribute("userid");
//                            boolean isVIP = ses.getAttribute("vip") != null;

                            UserObj userinfo = commonDataManager.getUserServiceInfoByUserName(user);
                            SerializerHelper.writeLong(userinfo.money, msgResponse);
                            SerializerHelper.writeInt(userinfo.avatar, msgResponse);
                            SerializerHelper.writeInt(userinfo.nickColor, msgResponse);
                            SerializerHelper.writeInt(userinfo.user_level, msgResponse); // joinUser level
                            SerializerHelper.writeBoolean(userinfo.isVIPAccount(), msgResponse);  // is Vip account
                            log.info(String.format("%s: Money (%d), Ava (%d), NickCo (%x), Lvl (%d), VIP (%d)",
                                    user, userinfo.money, userinfo.avatar,
                                    userinfo.nickColor, userinfo.user_level, userinfo.user_type));
                            i++;
                        }
                        SerializerHelper.writeInt(gt.tableType, msgResponse);
                        session.write(msgResponse);

                        // send updates for other players within table
                        if (joinOK && !gt.isPlaying) {
                            ServerService.broadcastTable(tableID, msgResponse, userName, CardConstant.TIENLEN_TYPE);
                        }
                        //                {
                        //                    for (int playerOrder=0; playerOrder<gt.playerList.size(); playerOrder++) {
                        //                        IoSession userSession = PIServer.VMSProtocolHandler.users.get(gt.playerList.get(playerOrder));
                        //                        if (userSession!=session)
                        //                            userSession.write(msgResponse);
                        //                    }
                        //                }
                        dataManager.closeConnection();
                        commonDataManager.closeConnection();
                        return;
                    }
                }
            }

            // return "failed to join table"
            logGame.error("Failed to join");
            SerializerHelper.writeByte(result, msgResponse);
            SerializerHelper.writeString("", msgResponse); // table ID
            SerializerHelper.writeLong(0, msgResponse); // betMoney
            SerializerHelper.writeInt(0, msgResponse); // No of user
            SerializerHelper.writeBoolean(false, msgResponse); // is playing
            session.write(msgResponse);

            dataManager.closeConnection();
            commonDataManager.closeConnection();

        } catch (Exception ex) {
            log.error("joinTable:", ex);
//            ex.printStackTrace();
        }

    }

    public void leaveTable(IoSession session,
            String userName, String tableId, boolean selfnotify) {

        boolean isJustFinished = false;

        log.info("UserLeave: TableID=" + tableId + ", text=" + userName + "; player=" + userName);

        //if (!RoomManager.tables.containsKey(tableId))
        if (!RoomManager.getInstance().isTableExist(tableId)) {
            return;
        }

        //GameTable gt = (GameTable) RoomManager.tables.get(tableId);
        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableId);

        if (gt == null) {
            log.debug("Invalid tableID ({})", tableId);
            return;
        }

//        IoSession outUserSession = session;      // hanv add
//        if (outUser != null && !outUser.equals(userName)) {
//            synchronized (PIServer.VMSProtocolHandler.sessions) {
//                outUserSession = PIServer.VMSProtocolHandler.users.get(outUser);
//            }
//        }

        if (gt.playerList.indexOf(userName) == 0) { // hanv: neu chu ban roi ban
            gt.password = null;                     // thi bo khoa ban
        }

        gt.playerList.remove(userName);

        // neu nguoi toi nhat thoat hoac ban chi con lai 1 nguoi --> danh lai 3 bich
        if ((gt.lastWinner != null && gt.lastWinner.equals(userName))
                || gt.playerList.size() <= 1) {                         // hanv add
            if (!gt.isPlaying) {
                gt.lastWinner = null;
                gt.isFirstDeal = true;
            }
        }

        // reset table (joined) players --> join (ready) again
        if ((!gt.isPlaying) && (gt.getNumberOfJoinedPlayers() > 0)) {
            gt.clearJoinedPlayerList(); //.remove(playerName);
//            log.info("Joined players are out!");
        }
        gt.isPlayerChanged = true;
        /**
         * Xóa session table attribute
         */
        if (session != null) {
            session.setAttribute("table", null);
        } else {
            logGame.debug("disconnect user leave table");
        }

        byte playerIndex = (byte) gt.playerJoined.indexOf(userName);

        if ((gt.isPlaying) && (playerIndex >= 0)) {
//            if (session == null) {
            gt.playerThoatNgang.add(userName);
//            }
            if (playerIndex == gt.nextTurnPlayer) {     // hanv: stop timer check d/v user thoat ngang
                gt.stopTurnTimeCheck();
            }
            if (gt.noCardsLeft[playerIndex] > 0) {
                // thoat ngang nhung chua het bai
                if (gt.noCardsLeft[playerIndex] == 13) {
                    broadcastTableMessage(gt.tableID, "", StringConstant.strChatCommand + 0 + " " + userName);
                    gt.playerCongBai.add(userName);
                }
                gt.noCardsLeft[playerIndex] = 0;
                isJustFinished = checkGameResult(gt, playerIndex, GAME_END_USER_QUIT);
            }
        }

        if (selfnotify && session.isConnected()) { // has not been disconnected
            Message msgResponse = new Message(BeanConstant.BT_RESPOND_LEAVE_BEAN, ServiceConstant.SYSTEM_SERVICE);
            SerializerHelper.writeString(tableId, msgResponse);
            SerializerHelper.writeString(userName, msgResponse);
            SerializerHelper.writeBoolean(true, msgResponse); // allow to quit
            session.write(msgResponse);
        }

        Message msgNotification = new Message(BeanConstant.BT_LEAVE_TABLE_BEAN, ServiceConstant.SYSTEM_SERVICE);
        SerializerHelper.writeString(tableId, msgNotification);
        SerializerHelper.writeString(userName, msgNotification);
        SerializerHelper.writeBoolean(playerIndex >= 0 ? gt.isPlaying : false, msgNotification); // is playing
        if ((gt.isPlaying) && (playerIndex >= 0)) {
            String nextPlayer = "";
            //log.info("UserLeave = " + playerName);
            /*
            if (gt.nextTurnPlayer == gt.playerJoined.indexOf(joinUser)) {
            nextPlayer = gt.getNextPlayer(joinUser);
            }
             *
             */
            /**
             * Fix lỗi thoát khỏi bàn
             */
            /*
            if(gt.lastTurnPlayer==gt.playerJoined.indexOf(joinUser)){
            gt.isNewTurn=true;
            }
             *
             */
            if (!isJustFinished) {
                /**
                 * Fix lỗi rời bàn khi đang chơi
                 */
                byte quitPlayerIdx = (byte) gt.playerJoined.indexOf(userName);
                //if ((gt.getFirstPlayer().equals(joinUser))
                // || (gt.lastTurnPlayer == quitPlayerIdx)
                //|| (gt.nextTurnPlayer == quitPlayerIdx))
                if (gt.nextTurnPlayer == quitPlayerIdx) {
                    boolean nextPlayerFound = gameLogic.findNextTurnPlayer(gt, quitPlayerIdx);

                    // trong truong hop chi co 1 nguoi danh trong luot 1 minh
                    // thi reset luot
                    if (nextPlayerFound && (gt.nextTurnPlayer == gt.lastTurnPlayer)) {
                        int playerInTurn = gt.countTurnMembers();
                        if ((!gt.isNewTurn) && (playerInTurn == 1)) {
                            gt.resetTurn();
                        }
                    }

                    nextPlayer = gt.getJoinedPlayerAt(gt.nextTurnPlayer);
                    if (!nextPlayerFound) {
                        // bị lỗi nếu không tìm thấy nextplayer, reset lượt đi
                        broadcastTableMessage(gt.tableID, "", userName + StringConstant.strBoLuotDi);
                        gt.resetTurn();
                    }
                } //                else if (gt.lastTurnPlayer == -1) {
                //                    nextPlayer = gt.getFirstPlayer();
                //                }
                else {
                    nextPlayer = gt.getJoinedPlayerAt(gt.nextTurnPlayer);
                }
            }
            SerializerHelper.writeString(nextPlayer, msgNotification);
            SerializerHelper.writeBoolean(gt.isNewTurn, msgNotification); // is new turn?
        }
        ServerService.broadcastTable(tableId, msgNotification, CardConstant.TIENLEN_TYPE);

        if (gt.isNewTurn && gt.soTienChat != 0) // hanv: tinh tien chat heo neu user thoat ngang
        {
            finalizeChatChong(gt);
        }

        if (gt.isPlaying) {
            if (isJustFinished) {
                finishGame(gt, WIN_TYPE_NORMAL);
            }
        } else {
            // reset bet money (0) if no ones left
            if (gt.playerList.size() == 0) {
                gt.betMoney = gt.betMoneyDefault;//Reset lại bằng số tiền cược của phòng
            }        //System.out.printf("Remaining users: %d (playing: %b)\n", gt.getNumberOfJoinedPlayers(), gt.isPlaying);
        }
    }

    public void kickOutOfTable(IoSession session, String userName, Message clientMsg) {

        String tableId = SerializerHelper.readString(clientMsg);
        String outUser = SerializerHelper.readString(clientMsg);

        if (!RoomManager.getInstance().isTableExist(tableId)) {
            return;
        }

        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableId);
        if (userName.equals(outUser) || gt == null || gt.isPlaying) {
            return;
        }

        gt.playerList.remove(outUser);
        gt.clearJoinedPlayerList();
        gt.isPlayerChanged = true;
        if (gt.lastWinner != null && gt.lastWinner.equals(outUser)) {
            gt.lastWinner = null;
            gt.isFirstDeal = true;
        }

        //IoSession outSession = PIServer.VMSProtocolHandler.users.get(outUser);
        IoSession outSession = PIServer.VMSProtocolHandler.getSessionByUserName(outUser);
        if (outSession != null) {
            String outTable = (String) outSession.getAttribute("table");
            if (outTable != null && outTable.equals(tableId)) {
                outSession.setAttribute("table", null);
                Message msgResponse = new Message(BeanConstant.RESPONSE_KICK_USER_OF_OUT_TABLE,
                        ServiceConstant.SYSTEM_SERVICE);
                SerializerHelper.writeString(tableId, msgResponse);
                outSession.write(msgResponse);
            }
        }

        Message msgNotification = new Message(BeanConstant.BT_LEAVE_TABLE_BEAN, ServiceConstant.SYSTEM_SERVICE);
        SerializerHelper.writeString(tableId, msgNotification);
        SerializerHelper.writeString(outUser, msgNotification);
        SerializerHelper.writeBoolean(false, msgNotification); // not playing
        ServerService.broadcastTable(tableId, msgNotification, outUser, CardConstant.TIENLEN_TYPE);
    }

    private void setBetMoney(IoSession session, int userID, Message clientMsg) {

        MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();

        String userName = (String) session.getAttribute("user");
        int roomNo = SerializerHelper.readInt(clientMsg);
        String tableId = SerializerHelper.readString(clientMsg);
        int betMoney = (int) SerializerHelper.readLong(clientMsg);
        /**
         * @todo:Kiểm tra có cần dùng roomNO không
         */
        String roomID = String.format("TL%03d", roomNo);
        if (RoomManager.getInstance().isTableExist(tableId)) {

            GameTable gt = RoomManager.getInstance().getTable(tableId);
            if (gt.isPlaying) {
                return;
            }
            int userType = (Integer) session.getAttribute("usertype");
            long money = commonDataManager.retrieveAccountBalance(userID);
            log.info("SET BET MONEY " + betMoney + " ROOMNO " + roomNo + " TABLE ID " + tableId);
            boolean betOK = (!gt.isPlaying)
                    && (!gt.playerList.isEmpty())
                    && (gt.playerList.get(0).equals(userName))
                    && (money >= betMoney)
                    && (betMoney <= CardConstant.MAX_BET_MONEY_LIMIT)
                    && (betMoney * 20 >= money)
                    && (userType != 3 || betMoney <= CardConstant.VIP3_MAX_BET_MONEY_LIMIT);
            // balance >= 110%betMoney : 10% tien xau

            Message msg = new Message(BeanConstant.BT_RESPOND_SET_BET_MONEY, ServiceConstant.GAME_CARD_SERVICE);
            SerializerHelper.writeInt(roomNo, msg);
            SerializerHelper.writeString(tableId, msg);
            SerializerHelper.writeBoolean(betOK, msg);
            SerializerHelper.writeLong(betMoney, msg);

            if (betOK) {
                // broadcast change of room's bet
                gt.betMoney = betMoney;
                gt.clearJoinedPlayerList();
                ServerService.broadcastTable(tableId, msg, CardConstant.TIENLEN_TYPE);
            } else {
                if (money < betMoney) {
                    SerializerHelper.writeInt(0, msg);
                    SerializerHelper.writeString(StringConstant.strNotEnoughBetMoneyError, msg);
                } else if (betMoney > CardConstant.MAX_BET_MONEY_LIMIT) {
                    SerializerHelper.writeInt(1, msg);
                    SerializerHelper.writeString(StringConstant.strMaxBetMoneyError, msg);
                } else if (betMoney * 20 < money) {
                    SerializerHelper.writeInt(2, msg);
                    SerializerHelper.writeString(String.format(StringConstant.strMinBetMoneyError, money / 20), msg);
                } else if (userType == 3 && betMoney > CardConstant.VIP3_MAX_BET_MONEY_LIMIT) {
                    SerializerHelper.writeInt(4, msg);
                    SerializerHelper.writeString(StringConstant.strMasterBetMoneyError, msg);
                }
                session.write(msg);
            }

        }
    }

    private void startGame(IoSession session, String userName, Message clientMsg) {
        //start a game
        int roomID = SerializerHelper.readInt(clientMsg);
        String tableID = SerializerHelper.readString(clientMsg);
        boolean statusReady = SerializerHelper.readBoolean(clientMsg);

        //GameTable gt = (GameTable) GameRoomManager.tables.get(tableID);
        GameTable gt = RoomManager.getInstance().getTable(tableID);
        if (gt == null || gt.isPlaying || !gt.playerList.contains(userName)) {
            // hanv add gt.isPlaying - fix bug ready status
            // chỉ user nào có trong playerList mới đc gửi START_BEAN
            return;
        }

        MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();
        long money = commonDataManager.retrieveAccountBalance(userName);
        int userType = (Integer) session.getAttribute("usertype");

        boolean isBetValid = ((money >= gt.betMoney) && (userType != 3 || gt.betMoney <= 10000));

        synchronized (gt.playerJoined) {
            if (statusReady && isBetValid) {
                if (!gt.playerJoined.contains(userName)) {
                    gt.playerJoined.add(userName);
                }
            } else {
                gt.playerJoined.remove(userName);
            }

            boolean isAllPlayersReady = checkAllPlayersReady(gt);
            log.info(tableID + ": ready (" + isAllPlayersReady + "), played changed: " + gt.isPlayerChanged
                    + ", isFirstDeal = " + gt.isFirstDeal);

            if (isAllPlayersReady && (!gt.isPlaying)) {
                gt.isPlaying = true;
                gt.playerThoatNgang = new ArrayList();
                gt.playerMatLuotDoQuaGio = new ArrayList();
                gt.isTruTienBatCong = false;

                if (gt.isPlayerChanged) {
                    //gt.isFirstDeal = true;
                    gt.isPlayerChanged = false;
                }
                //gt.isFinished=false;

                // reset chatchonglist
                gt.chatChongList.clear();
                //gt.chatChongIndex=0;
                gt.playerCongBai.clear();

                // the order of joined players must be according to the other of user list
                gt.clearJoinedPlayerList();
                gt.playerJoined.addAll(gt.playerList);

                // inform all players to start game
                Message msg = new Message(BeanConstant.BT_RESPOND_START_BEAN, ServiceConstant.GAME_CARD_SERVICE);
                SerializerHelper.writeInt(0, msg); // table type
                SerializerHelper.writeString(tableID, msg); // table id
                SerializerHelper.writeBoolean(true, msg); // started game
                SerializerHelper.writeString(gt.getJoinedPlayerAt(0), msg); // master playerName of table
                SerializerHelper.writeInt(CardConstant.DEFAULT_TL_TABLE_TIMER, msg); // table timer
                ServerService.broadcastTable(tableID, msg, CardConstant.TIENLEN_TYPE);

                // deal card
                int flagCheat = DEFAULT_FLAG_CHEAT;
                int cheatId = -1;
                for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
                    IoSession s = PIServer.VMSProtocolHandler.getSessionByUserName(gt.getJoinedPlayerAt(i));
                    userType = (Integer) s.getAttribute("usertype");
                    if (s.getAttribute("cheat") != null) {
                        flagCheat = (Integer) s.getAttribute("cheat");
                        cheatId = i;
                        s.removeAttribute("cheat");
                        break;
                    } else if (userType == 5) {
                        flagCheat = 12;
                        cheatId = i;
                    }
                }
//                if (session.getAttribute("cheat") != null && userType == 4) {
//                    flagCheat = (Integer) session.getAttribute("cheat");
//                    session.removeAttribute("cheat");
//                }
                String firstPlayer = dealCard(tableID, userName, flagCheat, cheatId);

                // charge fee to all users
                chargeFeeToUsers(gt);

                // check immediate win
                int winType = gameLogic.checkImmediateWin(gt);
                if (winType > 0) //gt.isFinished=true;
                {
                    finishGame(gt, winType);
                    gt.isFirstDeal = true;
                    gt.lastWinner = null;
                } else {
                    gt.monitorTurnTimeCheck(firstPlayer, CardConstant.DEAL_TIME_ADDING);
                }

            } else {
                Message msg = new Message(BeanConstant.BT_RESPOND_START_BEAN, ServiceConstant.GAME_CARD_SERVICE);
                SerializerHelper.writeInt(0, msg); // table type
                SerializerHelper.writeString(tableID, msg); // table id
                SerializerHelper.writeBoolean(false, msg); // start game?
                SerializerHelper.writeInt(gt.getNumberOfJoinedPlayers(), msg); // so nguoi ready
                for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
                    SerializerHelper.writeString(gt.getJoinedPlayerAt(i), msg);
                }

                if (!statusReady || isBetValid) {
                    // hanv: nếu user bỏ sẵn sàng hoặc gửi sẵn sàng hợp lệ (đủ tiền tham gia)
                    // thì gửi START_BEAN cho cả bàn
                    ServerService.broadcastTable(tableID, msg, CardConstant.TIENLEN_TYPE);
                } else {
                    // nếu user gửi sẵn sàng nhưng ko đủ tiền thì chỉ báo cho user đó biết
                    if (money < gt.betMoney) {
                        SerializerHelper.writeInt(0, msg);
                        SerializerHelper.writeString(StringConstant.strNotEnoughMoneyJoinError, msg);
                    } else if (userType == 3 && gt.betMoney > 10000) {
                        SerializerHelper.writeInt(1, msg);
                        SerializerHelper.writeString(StringConstant.strMasterBetMoneyJoinError, msg);
                    }
                    session.write(msg);
                }
//                ServerService.broadcastTable(tableID, msg, CardConstant.TIENLEN_TYPE);
            }
        }
    }

    private boolean checkAllPlayersReady(GameTable gt) {
        if (gt == null) {
            return false;
        } else {
            return ((gt.getNumberOfJoinedPlayers() == gt.playerList.size())
                    && (gt.getNumberOfJoinedPlayers() > 1));
        }
    }

    private String dealCard(String tableID, String username, int flagCheat, int cheatPlayer) {
        Message msg = new Message(BeanConstant.BT_DEAL_CARD_BEAN, ServiceConstant.GAME_CARD_SERVICE);

        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableID);
        if (gt == null) {
            return null;
        }

        if (flagCheat != 0) {
            log.warn("{}: CHEAT ENABLED {}", tableID, flagCheat);
        }

        gt.dealCards(flagCheat);

        byte[] tempcards = new byte[13];

        // cap nhat la bai cheat cho nguoi bat cheat
        if (flagCheat != 0) {
//            int cheatPlayer = gt.playerJoined.indexOf(username);
            if ((cheatPlayer != -1) && (cheatPlayer != 0)) {
                System.arraycopy(gt.cards[0], 0, tempcards, 0, 13);
                System.arraycopy(gt.cards[cheatPlayer], 0, gt.cards[0], 0, 13);
                System.arraycopy(tempcards, 0, gt.cards[cheatPlayer], 0, 13);
            }
        }

        String firstPlayer = gt.getFirstPlayer();
        gt.nextTurnPlayer = (byte) gt.playerJoined.indexOf(firstPlayer);

        for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
            gt.noCardsLeft[i] = 13;
            String user = gt.getJoinedPlayerAt(i);
            log.info(tableID + ": Deal card: " + user + " (" + (i + 1) + "/" + gt.getNumberOfJoinedPlayers() + ")");
            //log.info("Dealt");
            msg.getBuffer().setPosition(0);
//            System.out.println(msg.getBuffer().getPosition() + "    ");
            SerializerHelper.writeInt(0, msg); // table type
            SerializerHelper.writeString(tableID, msg);
            SerializerHelper.writeString(user, msg);

            System.arraycopy(gt.cards[i], 0, tempcards, 0, 13);
//            for (int j = 0; j < 13; j++) {
//                System.out.print(tempcards[j] + "    ");
//            }
//            System.out.println();
            SerializerHelper.write(tempcards, 0, 13, msg);
            //for (int j=0; j<13; j++) System.out.printf("%d ",tempcards[j]);
            //log.info();

            SerializerHelper.writeString(firstPlayer, msg);
            SerializerHelper.writeBoolean(gt.isFirstDeal, msg);
            //IoSession session = PIServer.VMSProtocolHandler.users.get(user);
            IoSession session = PIServer.VMSProtocolHandler.getSessionByUserName(user);
            if (session != null) {
//                System.out.println(session.getAttribute("user").toString());
                session.write(msg);
            }
        }
        recordGameCards(gt);
        return firstPlayer;
    }

    private void processMove(IoSession session, String userName, Message clientMsg) {

        boolean isFinished = false;
        String nextplayer = "";

        int tableType = SerializerHelper.readInt(clientMsg);
        String tableID = SerializerHelper.readString(clientMsg);
        String playerName = SerializerHelper.readString(clientMsg);
        int moveType = SerializerHelper.readInt(clientMsg);
        byte[] cards = SerializerHelper.readBytes(clientMsg);

        logGame.info("{}: Kiem luot danh cua {}", tableID, userName);

        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableID);
        if ((cards.length == 0) || (gt == null)) {
            logGame.error("No cards or game table not found");
            return;
        }

        byte pIndex = (byte) gt.playerJoined.indexOf(userName);
        if ((pIndex == -1) || (pIndex != gt.nextTurnPlayer)) {
            logGame.error("{}: [{}] Wrong turn submitted", tableID, userName);
            return;
        }

        java.util.Arrays.sort(cards);
        boolean validMove = gameLogic.checkMove(cards, gt, pIndex);

        if (!validMove) {
            updateClientCards(gt, userName, pIndex);

        } else {
            gt.stopTurnTimeCheck();
            /**
             * Trừ tiền chặt
             */
            /*
            if(gt.soTienChat!=0){
            updateBalance(gt.nguoiChat, gt.soTienChat);
            broadcastTableMessage(gt.tableID, gt.nguoiChat,"Chặt heo,cộng"+gt.soTienChat+" vixu");
            updateBalance(gt.nguoiBiChat, -gt.soTienChat);
            broadcastTableMessage(gt.tableID, gt.nguoiBiChat, "Bị chặt heo,trừ "+gt.soTienChat+" vixu");
            gt.soTienChat=0;
            gt.nguoiBiChat=null;
            gt.nguoiChat=null;
            }*/
            gt.isNewTurn = false;
            // remove cards played
            cleanPlayedCard(cards, gt, pIndex);
            boolean is3BFinish = (gt.playerResult[0] == null && cards.length == 1 && cards[0] == 12);
            if (gt.noCardsLeft[pIndex] == 0) {
                isFinished = checkGameResult(gt, pIndex,
                        (is3BFinish) ? GAME_END_3B : GAME_END_OUT_OF_CARD);
                if (gt.soTienChat > 0 && gt.nguoiChat.equals(userName)) {
                    finalizeChatChong(gt);
                }
            }

            if (!isFinished) {
                //Set quyen uu tien
                byte tmp = pIndex;
                boolean chatDuoc = false;
                for (byte playerCounter = 0; playerCounter < gt.getNumberOfJoinedPlayers() - 1; playerCounter++) {
                    tmp = (byte) ((tmp + 1) % gt.getNumberOfJoinedPlayers());
                    if (CardUtils.getCardValue(cards[cards.length - 1]) == CardConstant.PIG) {
                        if ((cards.length == 1) || (cards.length == 2)) {
                            chatDuoc = true;
                        }
                    } else if ((moveType == CardConstant.MOVE_BADOITHONG) || (moveType == CardConstant.MOVE_TUQUI)) {
                        chatDuoc = true;
                    }
                    /**
                     * @todo:Kiem tra truong hop 4 doi thong lon hon
                     */
                    if (chatDuoc) {
                        if (gameLogic.isContainBonDoiThong(gt, tmp)) {
                            logGame.debug("{} co 4 doi thong,duoc uu tien", gt.getJoinedPlayerAt(tmp));
                            gt.playerWithTurnPriority[tmp] = true;
                        }
                    }
                }
                // search player for next turn
                boolean nextPlayerFound = gameLogic.findNextTurnPlayer(gt, pIndex);
                // trong truong hop chi co 1 nguoi danh trong luot 1 minh
                // thi reset luot
                if (nextPlayerFound && (gt.nextTurnPlayer == pIndex)) {
                    int playerInTurn = gt.countTurnMembers();
                    if ((!gt.isNewTurn) && (playerInTurn == 1)) {
                        gt.resetTurn();
                    }
                }
                nextplayer = gt.getJoinedPlayerAt(gt.nextTurnPlayer);
                log.debug(tableID + ": Current: {}, next turn: {}", gt.getJoinedPlayerAt(pIndex), nextplayer);
            }
            Message msgResponse = new Message(BeanConstant.BT_MOVE_BEAN, ServiceConstant.GAME_CARD_SERVICE);
            SerializerHelper.writeInt(0, msgResponse); // table type
            SerializerHelper.writeString(tableID, msgResponse);
            SerializerHelper.writeString(userName, msgResponse);    // hanv: ten nguoi vua danh
            SerializerHelper.writeInt(moveType, msgResponse);
            SerializerHelper.write(cards, 0, cards.length, msgResponse);
            SerializerHelper.writeString(nextplayer, msgResponse);
            SerializerHelper.writeBoolean(gt.isNewTurn, msgResponse); // new turn
            ServerService.broadcastTable(tableID, msgResponse, CardConstant.TIENLEN_TYPE);
//            for (int i = 0; i < gt.playerList.size(); i++) {
//                String userID = (String) gt.playerList.get(i);
//                IoSession userSession = PIServer.VMSProtocolHandler.users.get(userID);
//                if (userSession != null) {
//                    log.debug("Deliver cards from {} to {}", userName, userID);
//                    System.out.println("Deliver cards from " + userName + " to " + userID);
//                    userSession.write(msgResponse);
//                } else {
//                    logGame.debug("Null session {}", userID);
//                    System.out.println("Null session " + userID);
//                }
//            }
            if (isFinished) {
                finishGame(gt, (is3BFinish) ? WIN_TYPE_3B : WIN_TYPE_NORMAL);
            } else {
                gt.monitorTurnTimeCheck(nextplayer, CardConstant.NORMAL_TIME_ADDING);
            }
            // update variables for turn control
            gt.lastTurnPlayer = pIndex;
        }
    }

    public void bypassMove(IoSession session, int tableType, String tableID,
            String userName, String userBypass) {
        boolean nextPlayerFound = false;

        if ((!userName.equals(userBypass))) {
            logGame.error("BYPASS: invalid user ({}<>{})", userBypass, userName);
            return;
        }

        String currentTable = null;
        if (session != null) {
            currentTable = (String) session.getAttribute("table");
        } else {
            // hanv: disconnect user
            LeaveUserObj leaveObj = PIServer.VMSProtocolHandler.getLeaveUserObjByUserName(userName);
            if (leaveObj != null) {
                currentTable = leaveObj.leaveTable;
                log.info("BYPASS: disconnect user {}", userName);
            } else {
                logGame.error("BYPASS: invalid session of user {}", userName);
                // todo: find next turn player or game die
                return;
            }
        }

        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableID);
        if ((gt == null)
                || (currentTable != null && !currentTable.equals(tableID))) {
            logGame.error("BYPASS: invalid table ID ({}<>{})", tableID, currentTable);
            return;
        }

        byte playerIndex = (byte) gt.playerJoined.indexOf(userBypass);
        if (playerIndex == -1) {
            return;
        }

        logGame.debug(tableID + ": " + userBypass + " bo luot ");

        if (userBypass.equals(gt.getJoinedPlayerAt(gt.nextTurnPlayer))) {    // hanv add
            if (!gt.isNewTurn) {
                gt.playerBypassedTurn[playerIndex] = true;
            }
        } else {
            logGame.error(tableID + ": {} bo luot KO HOP LE trong luot cua {}",
                    userBypass, gt.getJoinedPlayerAt(gt.nextTurnPlayer));
            return;
        }

        gt.stopTurnTimeCheck();
        gt.isFirstDeal = false;

        // neu bo luot khi duoc huong sai
        // count users who bypassed turns
        int playerInTurnCount = gt.countTurnMembers();
        if ((gt.isHuongSaiTurn) && (playerInTurnCount == 0)) {
            //gt.nextTurnPlayer = pIndex;
            String lastFinishedPlayer = gt.getLastFinishedPlayer();
            logGame.debug(tableID + ": Nguoi thang truoc: " + lastFinishedPlayer);
            if (lastFinishedPlayer != null) {
                // nguoi dau tien tu nguoi thang truoc do
                byte pIndex = (byte) gt.playerJoined.indexOf(lastFinishedPlayer);
                if (pIndex == gt.lastTurnPlayer) {
                    logGame.debug(tableID + ": Xet tiep nguoi duoc huong sai tu " + gt.getJoinedPlayerAt(pIndex));
                    playerIndex = pIndex;
                    gt.resetTurn();
                } else {
                    // chua ai thang, nguoi ket thuc luot cu duoc di tiep
                    logGame.debug(tableID + ": Luot moi do da bat bai nguoi toi nhat "
                            + gt.getJoinedPlayerAt(playerIndex));
                    gt.resetTurn();
                    nextPlayerFound = true;
                }
            }
        }

        if (!nextPlayerFound) {
            nextPlayerFound = gameLogic.findNextTurnPlayer(gt, playerIndex);
        }

//        for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
//            if (i == playerIndex) {
//                System.out.print("Current: ");
//            }
//            log.info(gt.getJoinedPlayerAt(i));
//        }

        if (!nextPlayerFound) {
            // nguoi danh truoc da thoat hoac het bai
            //if ((gt.lastTurnPlayer != -1) && (gt.noCardsLeft[gt.lastTurnPlayer] == 0)) {
            gt.resetTurn();
            for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
                playerIndex = (byte) ((playerIndex + 1) % gt.getNumberOfJoinedPlayers());
                if (gt.noCardsLeft[playerIndex] != 0) {
                    break;
                }
            }
            gt.nextTurnPlayer = playerIndex;
            //}
        }

        if (!gt.isNewTurn) {
            gt.isNewTurn = gt.nextTurnPlayer == gt.lastTurnPlayer;
            if (gt.isNewTurn) {
                gt.resetTurn();
            }
        }

//        else {
//            gt.playerBypassedTurn[playerIndex] = true;
//        }

        logGame.debug(tableID + ": Ke tiep: {} {}",
                gt.getJoinedPlayerAt(gt.nextTurnPlayer),
                gt.isNewTurn ? " - luot moi" : "");

        if (gt.isNewTurn) {
            if (gt.soTienChat != 0) {
                finalizeChatChong(gt);
//                updateBalance(gt.nguoiChat, gt.soTienChat);
//                broadcastTableMessage(gt.tableID, gt.nguoiBiChat,"-"+gt.soTienChat);
//                updateBalance(gt.nguoiBiChat, -gt.soTienChat);
//                broadcastTableMessage(gt.tableID, gt.nguoiChat,"+");
//                gt.soTienChat=0;
//                gt.nguoiBiChat=null;
//                gt.nguoiChat=null;
//
//                // reset log
//                gt.chatChongList.clear();
                //gt.chatChongIndex=0;
            }
            //gt.chatChongList=new ArrayList();
            //gt.chatChongIndex=0;
        }

        Message msgResponse = new Message(BeanConstant.BT_IGNORE_BEAN, ServiceConstant.GAME_CARD_SERVICE);
        String nextPlayer = gt.getJoinedPlayerAt(gt.nextTurnPlayer);
        SerializerHelper.writeInt(tableType, msgResponse);
        SerializerHelper.writeString(tableID, msgResponse);
        SerializerHelper.writeString(userBypass, msgResponse);
        SerializerHelper.writeString(nextPlayer, msgResponse);
        SerializerHelper.writeBoolean(gt.isNewTurn, msgResponse); // new turn
        ServerService.broadcastTable(tableID, msgResponse, CardConstant.TIENLEN_TYPE);

        gt.monitorTurnTimeCheck(nextPlayer, CardConstant.NORMAL_TIME_ADDING);

//        updateClientCards(gt, gt.getJoinedPlayerAt(playerIndex), playerIndex);

    }

    private void finishGame(GameTable gt, int winType) {
        logGame.info(gt.tableID + ": FINISH GAME (WinType=" + winType + ") - " + gt.getNumberOfJoinedPlayers() + " players");
//        GameTable gt = (GameTable) GameRoomManager.tables.get(tableID);
        if (gt == null) {
            return;
        }
        gt.stopTurnTimeCheck();
        if (gt.soTienChat != 0) {
            finalizeChatChong(gt);
        }

        if (winType == WIN_TYPE_NORMAL) {
            int i = gt.playerJoined.indexOf(gt.playerResult[0]);    // chi so cua nguoi ve nhat
            if (gt.noCardsLeft[i] == 1 && gt.cards[i][0] == 12) {
                winType = WIN_TYPE_3B;
            }
        }

        if (winType == WIN_TYPE_NORMAL || winType == WIN_TYPE_3B) {
            finalizeUngList(gt);    // hanv add: cập nhật lại danh sách úng hàng
        }
        String bonus = null;
//        if (winType == WIN_BINH_THUONG && gt.playerCongBai.isEmpty()) {
//            IoSession session = PIServer.VMSProtocolHandler.getSessionByUserName(gt.playerResult[0]);
//            if (session != null) {
//                int avatar = (Integer) session.getAttribute("avatar");
//                if ((gt.betMoney < 10000) && (avatar == 3)) {
//                    bonus = StringConstant.strBonusCommand + StringConstant.strEventBonus;
//                    broadcastSingleMessage(session, gt.tableID, "", bonus);
//                }
//            }
//        }

        VCoreUserDataProvider dataManager = new VCoreUserDataProvider();
        MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();
        try {
            // notify player results
            Message msgResponse = new Message(BeanConstant.BT_FINISH_BEAN, ServiceConstant.GAME_CARD_SERVICE);

            SerializerHelper.writeInt(0, msgResponse); // table type
            SerializerHelper.writeString(gt.tableID, msgResponse);
            // win type : 0 thang thua binh thuong, 1 an trang
            SerializerHelper.writeInt(winType, msgResponse); //win type

            SerializerHelper.writeInt(gt.getNumberOfJoinedPlayers(), msgResponse);
            // balance info of player
            for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
//                if (i == 0) {
//                    try {
//                        String strWin = null;
//                        switch (winType) {
//                            case WIN_BINH_THUONG:
//                                if (!gt.playerCongBai.isEmpty()) {
//                                    strWin = StringConstant.strWin13;
//                                }
//                                break;
//
//                            case GAME_END_3_SPADE:
//                                strWin = StringConstant.strWin3spade;
//                                break;
//
//                            default:
//                                strWin = StringConstant.strWinAtDeal;
//                        }
//
//                        if (strWin != null) {
//                            String winningMsg = String.format(
//                                    //  TL000518
//                                    "Chúc mừng %s đã %s tại P%d B%d!!!",
//                                    gt.playerResult[i], strWin,
//                                    Integer.parseInt(gt.tableID.substring(2, 6), 10),
//                                    Integer.parseInt(gt.tableID.substring(6, 8), 10) + 1);
//
//                            ItemFeatureService.addBroadcastMessage(winningMsg, ItemServiceConstant.LOW_PRIORITY, ItemServiceConstant.BROADCAST_TIENLEN);
//                        }
//                    } catch (Exception e) {
//                    }
//                }

                logGame.info((i + 1) + ". " + gt.playerResult[i] + " = " + gt.playerBalance[i]);
                //Update win count
                int winResult = 0;
                if (i == 0) {
                    winResult = 1;
                } else if (gt.playerBalance[i] < 0) {
                    winResult = -1;
                }

                // hanv: có 3 TH tính tiền cuối ván:
                // 1. Không có người cóng (đến cuối ván mới xác định được người thua)
                // 2. Có 2-3 người cóng -> kết thúc ván ngay
                // 3. 4 tay, có 1 người cóng, người được tính là người về nhì, ba

                commonDataManager.updateGameHistory(gt.playerResult[i], winResult, gt.game_id, gt.game_date, CardConstant.TIEN_LEN_SERVICE_ID);
                // update players' balance
                int balance = commonDataManager.retrieveAccountBalance(gt.playerResult[i]);

                // TNK: Kiem tra ket qua x2
//                if (gt.checkEffect(gt.playerResult[i], EffectType.X2_BET) && gt.playerBalance[i] > 0) {
//                    gt.playerBalance[i] *= 2;
//                }

                if (ServerService.isEventStarted && i == 0 && bonus != null) {
                    gt.playerBalance[i] += gt.betMoney * 20 / 100;
                    logGame.info("Tang EVENT (thuong 20%): {}", gt.betMoney * 20 / 100);
                }

                if (gt.playerCongBai.isEmpty() || !gt.isTruTienBatCong
                        || (i != 0 && !gt.playerCongBai.contains(gt.playerResult[i]))) {
                    // update players' balance
                    /**
                     * Nếu tiền bị trừ âm thì set bằng 0
                     */
                    balance += gt.playerBalance[i];
                    if (balance < 0) {
                        balance = 0;
                    }

                    // ghi log úng hàng
                    String strUng = "";
                    int playerIndex = gt.playerJoined.indexOf(gt.playerResult[i]);
                    if (playerIndex >= 0) {
                        for (int j = 0; j < gt.ungHangList[playerIndex].length; j++) {
                            if (gt.ungHangList[playerIndex][j] < 0) {
                                if (strUng.equals("")) {
                                    strUng = ", úng";
                                }
                                strUng += " " + gt.ungHangList[playerIndex][j] * (-1);
                                switch (j) {
                                    case 0:
                                        strUng += " heo đen";
                                        break;
                                    case 1:
                                        strUng += " heo đỏ";
                                        break;
                                    case 2:
                                        strUng += " ba đôi thông";
                                        break;
                                    case 3:
                                        strUng += " bốn đôi thông";
                                        break;
                                    case 4:
                                        strUng += " tứ quý";
                                        break;
                                    case 5:
                                        strUng += " ba bích";
                                        break;
                                }
                            }
                        }
                    }

                    if (i == 0) {
                        if (winType == WIN_TYPE_3B) {
                            strUng += " ba bích";
                        }
                        if (winType >= 11) {
                            strUng += " ăn trắng";
                        }
                    }

                    commonDataManager.setUserMoney(gt.playerResult[i], balance,
                            String.format(StringConstant.strPlayerOrder + strUng, gt.playerResult[i], i + 1, gt.playerBalance[i]), gt.playerBalance[i], CardConstant.PvP_MONEY_TYPE, CardConstant.TIEN_LEN_SERVICE_ID, gt.game_id);
                    logGame.info("User " + gt.playerResult[i] + " balance " + balance);

                }
                // notify updated balance
                SerializerHelper.writeString(gt.playerResult[i], msgResponse);
                SerializerHelper.writeLong(gt.playerBalance[i], msgResponse);
                SerializerHelper.writeLong(balance, msgResponse);

                byte[] baiconlai = new byte[13];
                int count = 0;
                int playerIndex = gt.playerJoined.indexOf(gt.playerResult[i]);
                if (playerIndex >= 0) {
                    SerializerHelper.write(gt.ungHangList[playerIndex], 0, 6, msgResponse);
                    logGame.debug("Ung hang {}", gt.ungHangList[gt.playerJoined.indexOf(gt.playerResult[i])]);
                    //                for (int j=0; j<6; j++) {
                    //                    System.out.print(gt.ungHangList[gt.playerJoined.indexOf(gt.playerResult[i])][j] + "-");
                    //                }

                    /**
                     * Gửi danh sách lá bài còn lại xuống
                     */
                    // hanv: ko trả về bài của người về nhất & thoát ngang
                    if (i != 0 && !gt.playerThoatNgang.contains(gt.playerResult[i])) {
                        for (int j = 0; j < gt.cards[gt.playerJoined.indexOf(gt.playerResult[i])].length; j++) {
                            if (gt.cards[gt.playerJoined.indexOf(gt.playerResult[i])][j] != 0) {
                                baiconlai[count] = gt.cards[gt.playerJoined.indexOf(gt.playerResult[i])][j];
                                count++;
                            }
                        }
                    }
                } else {
                    logGame.debug("Player {} not found in table {}",
                            gt.playerResult[i], gt.tableID);
                    for (int wcnt = 0; wcnt < 6; wcnt++) {
                        SerializerHelper.writeByte((byte) 0, msgResponse);
                    }
                }
                SerializerHelper.writeInt(count, msgResponse);
                SerializerHelper.write(baiconlai, 0, count, msgResponse);
            }
            // info of remaining users
            int numOfLelfPlayer = gt.getNumberOfJoinedPlayers() - gt.playerThoatNgang.size();
            SerializerHelper.writeInt(numOfLelfPlayer, msgResponse);
            for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
                String player = gt.getJoinedPlayerAt(i);
                if (!gt.playerThoatNgang.contains(player)) {
                    SerializerHelper.writeString(player, msgResponse);
                }
            }
            ServerService.broadcastTable(gt.tableID, msgResponse, CardConstant.TIENLEN_TYPE);
        } finally {
            dataManager.closeConnection();
            commonDataManager.closeConnection();
        }

        gt.isPlaying = false;   // hanv: fix bug user join table but invisible
        boolean haveNewPlayer = false;
        for (int i = 0; i < gt.getNumberOfPlayers(); i++) {
            String player = (String) gt.playerList.get(i);
            if (!gt.playerJoined.contains(player) || gt.playerThoatNgang.contains(player)) {
                // hanv: user nào ko có trong playerJoined hoặc có nhưng đã thoát ngang
                // thì hết ván mới cho user khác thấy
                haveNewPlayer = true;
                break;
            }
        }

//        gt.clearJoinedPlayerList();
//        gt.isPlaying = false;

        // set winner's name
        String winner = gt.playerResult[0];
        if (gt.playerThoatNgang.indexOf(winner) == -1
                && gt.playerThoatNgang.size() < gt.getNumberOfJoinedPlayers() - 1) {
            gt.lastWinner = winner;
        } else {
            // neu nguoi toi nhat thoat ngang --> danh lai 3 bich
            // hanv: nếu ván kết thúc do cả bàn thoát ngang chỉ còn lại 1 người -> đánh lại 3 bích
            gt.lastWinner = null;
            gt.isFirstDeal = true;
        }

        gt.clearJoinedPlayerList();
//        gt.isPlaying = false; // rem by hanv

        removePlayerInLeaveQueue(gt);

        if (haveNewPlayer) {
            Message msgResponse = new Message(BeanConstant.BT_RESPOND_JOIN_TABLE_BEAN, ServiceConstant.SYSTEM_SERVICE);
            SerializerHelper.writeByte((byte) 1, msgResponse);
            SerializerHelper.writeString(gt.tableID, msgResponse);
            SerializerHelper.writeLong(gt.betMoney, msgResponse);
            SerializerHelper.writeInt(gt.getNumberOfPlayers(), msgResponse);
            SerializerHelper.writeBoolean(false, msgResponse);  // not playing

            int i = 0;
            while (i < gt.getNumberOfPlayers()) {
                String user = (String) gt.playerList.get(i);
                SerializerHelper.writeString(user, msgResponse);

                UserObj userinfo = commonDataManager.getUserServiceInfoByUserName(user);
                SerializerHelper.writeLong(userinfo.money, msgResponse);
                SerializerHelper.writeInt(userinfo.avatar, msgResponse);
                SerializerHelper.writeInt(userinfo.nickColor, msgResponse);
                SerializerHelper.writeInt(userinfo.user_level, msgResponse); // joinUser level
                SerializerHelper.writeBoolean(userinfo.isVIPAccount(), msgResponse);  // is Vip account
                log.info(String.format("%s: Money (%d), Ava (%d), NickCo (%x), Lvl (%d), VIP (%d)",
                        user, userinfo.money, userinfo.avatar,
                        userinfo.nickColor, userinfo.user_level, userinfo.user_type));
                i++;
            }
            SerializerHelper.writeInt(gt.tableType, msgResponse);
            ServerService.broadcastTable(gt.tableID, msgResponse, CardConstant.TIENLEN_TYPE);
        }
    }

    public void removePlayerInLeaveQueue(GameTable gt) {
        for (int i = 0; i < gt.getNumberOfPlayers(); i++) {
            String userName = (String) gt.playerList.get(i);
            synchronized (PIServer.VMSProtocolHandler.leaveUserQueue) {
                if (PIServer.VMSProtocolHandler.leaveUserQueue != null && !PIServer.VMSProtocolHandler.leaveUserQueue.isEmpty()) {
                    if (PIServer.VMSProtocolHandler.leaveUserQueue.containsKey(userName)) {
                        log.info("Remove user {} for game over", userName);
                        leaveTable(null, userName, gt.tableID, false);
                        PIServer.VMSProtocolHandler.leaveUserQueue.remove(userName);
                    }
                }
            }
        }
    }

    /**
     * Cập nhật tiền thắng cược ngay
     * @param player joinUser cập nhật
     * @param money  money số tiền thắng
     */
    private int updateBalance(String player, int money, String description, int gameid) {
        int balance = 0;
//        MySQLCommonDataProvider dataManager = new MySQLCommonDataProvider();
        MySQLCommonDataProvider dataManager = DbManager.GetCommonDataManager();
        try {
            balance = dataManager.retrieveAccountBalance(player);
            balance += money;
            dataManager.setUserMoney(player, balance, description, money, CardConstant.PvP_MONEY_TYPE, CardConstant.TIEN_LEN_SERVICE_ID, gameid);
        } finally {
            dataManager.closeConnection();
        }

        // return money - ((balance < 0) ? balance : 0);

        // tra ve so tien thuc su duoc cap nhat
        if (balance >= 0) {
            return Math.abs(money);
        }
        return balance - money;
    }

    private void cleanPlayedCard(byte[] cards, GameTable gt, byte playerIndex) {
        for (int i = 0; i < cards.length; i++) {
            for (int j = 0; j < gt.cards[playerIndex].length; j++) {
                if (cards[i] == gt.cards[playerIndex][j]) {
                    gt.cards[playerIndex][j] = 0;
                    gt.noCardsLeft[playerIndex]--;
                    break;
                }
            }
        }
    }

    private void cleanAllPlayedCard(GameTable gt, byte playerIndex) {
        for (int i = 0; i < 13; i++) {
            gt.cards[playerIndex][i] = 0;
        }
    }

    // kiem tra toi nhat 3 bich
//    private boolean checkThreeSpadeFinish(GameTable gt, byte[] cards) {
//        return (gt.playerResult[0] == null && cards.length == 1 && cards[0] == 12);
//    }
    private boolean checkGameResult(GameTable gt, byte curPlayerIndex, int gameEndType) {

        byte finishedPlayer = 0;
        int betMoney = 0;
        int ungMoney = 0;
        boolean isFinished;
        int ungMTotal = 0;
        String description;
        int numOfJoinedPlayers = gt.getNumberOfJoinedPlayers();

        float thirstRatio = (numOfJoinedPlayers == 3) ? THREE_PLAYER_RATIO : FOUR_PLAYER_RATIO; // ti le thua nhi/ba
//        float fourthRatio = (numOfJoinedPlayers == 3) ? (1 - thirstRatio) : 1;      // ti le thua bet

        int thirstMoney = Math.round(gt.betMoney * thirstRatio);    // so tien thua nhi/ba (neu danh 3/4 tay)
        int fourthMoney = (numOfJoinedPlayers == 3)
                ? (gt.betMoney - thirstMoney) : gt.betMoney;    // so tien thua bet
        int congMoney = gt.betMoney * 2;    // tiền thua cóng

        String playerName = gt.getJoinedPlayerAt(curPlayerIndex);
        logGame.debug("Check finish at player = " + playerName);
        // count finished players
        for (int i = 0; i < numOfJoinedPlayers; i++) {
            if (gt.noCardsLeft[i] == 0) {
                finishedPlayer++;
            }
        }
//        if (gt.playerThoatNgang.size() > 0) {
//            for (int i = 0; i < gt.playerThoatNgang.size(); i++) {
//                log.info("user thoat ngang " + gt.playerThoatNgang.get(i));
//            }
//        }
        logGame.debug("CHECK RESULT - Finished player: " + finishedPlayer);

        switch (gameEndType) {
            case GAME_END_USER_QUIT: // nguoi choi thoat ngang
                for (int i = numOfJoinedPlayers - 1; i >= 0; i--) {
                    if (gt.playerResult[i] == null) {
                        gt.playerResult[i] = playerName;
                        break;
                    }
                }
                break;

            case GAME_END_OUT_OF_CARD: // nguoi choi het bai
                if (gt.noCardsLeft[curPlayerIndex] != 0) {
                    return false;
                }
                /**
                 * Kiểm tra cóng bài
                 */
                if (finishedPlayer >= 1) {
                    for (int i = 0; i < numOfJoinedPlayers; i++) {
                        if (gt.noCardsLeft[i] == 13) {
                            logGame.debug(gt.getJoinedPlayerAt(i) + " cong bai ");
                            broadcastTableMessage(gt.tableID, "", StringConstant.strChatCommand + 0 + " " + gt.getJoinedPlayerAt(i));
                            for (int j = numOfJoinedPlayers; j > 0; j--) {
                                if (gt.playerResult[j - 1] == null) {
                                    gt.playerResult[j - 1] = gt.getJoinedPlayerAt(i);
                                    break;
                                }
                            }
                            finishedPlayer++;
                            gt.playerCongBai.add(gt.getJoinedPlayerAt(i));
                            gt.noCardsLeft[i] = 0;
                        }
                    }
                }

                // bat dau xet luot huong sai
                gt.isHuongSaiTurn = true;

                // thong bao ket qua thang
                for (int i = 0; i < numOfJoinedPlayers; i++) {
                    if (gt.playerResult[i] == null) {
                        if (numOfJoinedPlayers - finishedPlayer > 1) //broadcastTableMessage(gt.tableID, "", playerName+ StringConstant.strPlayerWin+(i+1));
                        {
                            broadcastTableMessage(gt.tableID, "", StringConstant.strChatCommand + (i + 1) + " " + playerName);
                        }
                        gt.playerResult[i] = playerName;
                        break;
                    }
                }
                break;

            case GAME_END_3B:  // toi nhat 3 bich

                int j = 1;
                gt.playerResult[0] = playerName;
                for (int i = 0; i < numOfJoinedPlayers; i++) {
                    if (i != curPlayerIndex) {
                        gt.playerCongBai.add(gt.getJoinedPlayerAt(i));
                        gt.playerResult[j++] = gt.getJoinedPlayerAt(i);
                    }
                }
                break;
        }

        isFinished = (finishedPlayer >= (numOfJoinedPlayers - 1) || gameEndType == GAME_END_3B);
        if (isFinished) {
            if (gameEndType != GAME_END_3B) {
                for (int i = 0; i < numOfJoinedPlayers; i++) {
                    if (gt.noCardsLeft[i] != 0) {
                        logGame.debug("Only 1 player left: " + gt.getJoinedPlayerAt(i));
                        for (int j = numOfJoinedPlayers - 1; j >= 0; j--) {
                            if (gt.playerResult[j] == null) {
                                gt.playerResult[j] = gt.getJoinedPlayerAt(i);
                                if (j == 0 && gt.noCardsLeft[i] == 1 && gt.cards[i][0] == 12) {
//                                    int l = 1;
                                    for (int k = 0; k < numOfJoinedPlayers; k++) {
                                        if (k != i) {
                                            gt.playerCongBai.add(gt.getJoinedPlayerAt(k));
//                                            gt.playerResult[l++] = gt.getJoinedPlayerAt(k);
                                        }
                                    }
                                    gameEndType = GAME_END_3B;
                                }
                                break;
                            }
                        }
                        break;
                    }
                }
            }
        } else if (!gt.playerCongBai.isEmpty() && gt.playerResult[0] != null) {
            // hanv: nếu có người cóng & người về nhất thì trừ tiền ngay
            // nếu chưa có người nhất nhưng có người cóng (do thoát ngang) thì cuối ván mới trừ
            // nếu có 2 người bị cóng do thoát ngang -> chưa có người về nhất
            // nếu có 2,3 người bị đánh cóng -> kết thúc ván, trừ tiền ở finishGame
            // -> chỉ có 1 TH duy nhất: 4 tay, 1 người bị đánh cóng
            ungMTotal = 0;
            gt.isTruTienBatCong = true;
            int collectedMoney = 0; // so tien thuc su lay duoc cua player bi cong

            // TNK: Kiem tra ket qua x2
//            boolean hasX2Bai = false;
//            boolean hasX2Thui = false;
//            if (gt.checkEffect(playerName, EffectType.X2_BET)) {
//                hasX2Bai = true;
//            }
//            if (gt.checkEffect(playerName, EffectType.X2_THUI_BAI)) {
//                hasX2Thui = true;
//            }

            int i = gt.playerJoined.indexOf(gt.playerCongBai.get(0));
            String congPlayer = gt.getJoinedPlayerAt(i);
            ungMTotal += congMoney;
            ungMoney = gameLogic.getMoneyUngDoChoi(gt, i);
            ungMTotal += ungMoney; // Cộng thêm tiền úng

            String ungDes = "";
            if (ungMoney > 0) {
                ungDes = ", úng";
                for (int j = 0; j < gt.ungHangList[i].length; j++) {
                    if (gt.ungHangList[i][j] > 0) {
                        ungDes += " " + gt.ungHangList[i][j];
                        switch (j) {
                            case 0:
                                ungDes += " heo đen";
                                break;
                            case 1:
                                ungDes += " heo đỏ";
                                break;
                            case 2:
                                ungDes += " ba đôi thông";
                                break;
                            case 3:
                                ungDes += " bốn đôi thông";
                                break;
                            case 4:
                                ungDes += " tứ quý";
                                break;
                            case 5:
                                ungDes += " ba bích";
                                break;
                        }
                    }
                }
            }

//            MySQLCommonDataProvider dataManager = new MySQLCommonDataProvider();
            MySQLCommonDataProvider dataManager = DbManager.GetCommonDataManager();
            int balance = dataManager.retrieveAccountBalance(congPlayer);
            collectedMoney = Math.min(balance, ungMTotal);

            description = String.format(StringConstant.strCongBaiThua, congPlayer, collectedMoney);
            dataManager.setUserMoney(congPlayer, balance - collectedMoney, description + ungDes, collectedMoney * -1, CardConstant.PvP_MONEY_TYPE, CardConstant.TIEN_LEN_SERVICE_ID, gt.game_id);
            broadcastTableMessage(gt.tableID, "", description);

//            if (hasX2Bai && congMoney > 0) {
//                collectedMoney += congMoney;
//            }
//            if (hasX2Thui && ungMoney > 0) {
//                collectedMoney += ungMoney;
//            }

            description = String.format(StringConstant.strCongBaiThang, playerName, collectedMoney);
            balance = dataManager.retrieveAccountBalance(playerName);
            dataManager.setUserMoney(playerName, balance + collectedMoney, description, collectedMoney, CardConstant.PvP_MONEY_TYPE, CardConstant.TIEN_LEN_SERVICE_ID, gt.game_id);
            broadcastTableMessage(gt.tableID, "", description);
            gt.playerBalance[0] = collectedMoney;
            gt.playerBalance[3] = -collectedMoney;

            /**
             * Gửi về danh sách lá bài người cóng
             */
//            if (!gt.playerCongBai.isEmpty()) {
//                Message msgCong = new Message(BeanConstant.BT_CONGBAI, ServiceConstant.GAME_CARD_SERVICE);
//                byte[] baiconlai = new byte[13];
//                int count = 0;
////                String userID = (String) playerName;
//                int idx = gt.playerJoined.indexOf(playerName);
//                if (idx != -1) {
//                    for (int j = 0; j < gt.cards[idx].length; j++) {
//                        if (gt.cards[idx][j] != 0) {
//                            baiconlai[count] = gt.cards[idx][j];
//                            count++;
//                        }
//                    }
//                }
//                SerializerHelper.write(baiconlai, 0, count, msgCong);
//                //IoSession userSession = PIServer.VMSProtocolHandler.users.get(userID);
//                IoSession userSession = PIServer.VMSProtocolHandler.getSessionByUserName(playerName);
//                if (userSession != null) {
//                    userSession.write(msgCong);
//                }
//            }
        }

        // calc bonus
        if (isFinished) {
            logGame.debug("GAME SCORE");

            // so tien cua player da xep thu tu luc het van (chua bi tru)
            int[] playerMoney = new int[numOfJoinedPlayers];
//            MySQLCommonDataProvider dataManager = new MySQLCommonDataProvider();
            MySQLCommonDataProvider dataManager = DbManager.GetCommonDataManager();
            try {
                for (int i = 0; i < playerMoney.length; i++) {
                    playerMoney[i] = dataManager.retrieveAccountBalance(gt.playerResult[i]);
                }
            } finally {
                dataManager.closeConnection();
            }

            for (int playerOrder = 0; playerOrder < numOfJoinedPlayers; playerOrder++) {
                switch (numOfJoinedPlayers) {

                    case CardConstant.TWO_PLAYERS:
                        if (gt.playerCongBai.isEmpty()) {
                            betMoney = fourthMoney;
                        } else {
                            betMoney = congMoney;
                        }

                        // neu player ko con du tien chung thi con bao nhieu lay bay nhieu
                        if (playerOrder == CardConstant.FIRST) {
                            betMoney = Math.min(betMoney, playerMoney[1]);
                        } else {
                            betMoney = Math.max(-betMoney, -playerMoney[1]);
                        }
                        break;

                    case CardConstant.THREE_PLAYERS:
                        switch (playerOrder) {

                            case CardConstant.FIRST:
                                betMoney = 0;
                                for (int i = 1; i < 3; i++) {   // hanv: cộng tiền 2 người về nhì, ba
                                    if (!gt.playerCongBai.isEmpty()) {
                                        if (gt.playerCongBai.contains(gt.playerResult[i])) {
                                            // neu player ko con du tien chung thi con bao nhieu lay bay nhieu
                                            betMoney += Math.min(playerMoney[i], congMoney);
                                        } else {
                                            if (i == CardConstant.THIRST) { // thoát ngang trước khi có nhà bị cóng -> về bét
                                                betMoney += Math.min(playerMoney[i], fourthMoney);
                                            } else {
                                                betMoney += Math.min(playerMoney[i], thirstMoney);
                                            }
                                        }
                                    } else {
                                        if (i == CardConstant.SECOND) {
                                            betMoney += Math.min(playerMoney[i], thirstMoney);
                                        } else {
                                            betMoney += Math.min(playerMoney[i], fourthMoney);
                                        }
                                    }
                                }
                                break;

                            case CardConstant.SECOND:
                            case CardConstant.THIRST:
                                betMoney = 0;
                                if (!gt.playerCongBai.isEmpty() && gt.playerCongBai.contains(gt.playerResult[playerOrder])) {
                                    betMoney = congMoney;
                                } else {
                                    betMoney = (playerOrder == CardConstant.SECOND) ? thirstMoney : fourthMoney;
                                }
                                betMoney = Math.max(-betMoney, -playerMoney[playerOrder]);
                                break;

//                            case CardConstant.THIRST:
//                                betMoney = 0;
//                                if ((gt.playerCongBai.size() > 0) || (gt.playerThoatNgang.size() > 0)) {
//                                    if ((gt.playerCongBai.contains(gt.playerResult[2])) && (!gt.isTruTienBatCong)) {
//                                        betMoney -= gt.betMoney * 2;
//                                    } else if (gt.playerThoatNgang.contains(gt.playerResult[2])) {
//                                        betMoney -= gt.betMoney;
//                                    } else {
//                                        betMoney -= 3 * gt.betMoney / 4;
//                                    }
//                                } else {
//                                    betMoney = -3 * gt.betMoney / 4;
//                                }
//                                break;
                        }
                        break;

                    case CardConstant.FOUR_PLAYERS:
                        switch (playerOrder) {
                            case CardConstant.FIRST:
                                betMoney = 0;
                                if (!gt.playerCongBai.isEmpty()) {
                                    for (int i = 1; i < numOfJoinedPlayers; i++) {
                                        if (gt.playerCongBai.contains(gt.playerResult[i])) {
                                            // neu player ko con du tien chung thi con bao nhieu lay bay nhieu
                                            betMoney += Math.min(playerMoney[i], congMoney);
                                        } else if (gt.playerCongBai.size() == 2) {
                                            // nếu giết 2 nhà: lấy tiền cóng 2 nhà + tiền thua nhì của nhà về nhì
                                            // nếu nhà về nhì thoát ngang thì bị xử thua bét
                                            if (gt.playerThoatNgang.contains(gt.playerResult[i])) {
                                                betMoney += Math.min(playerMoney[i], fourthMoney);
                                            } else {
                                                betMoney += Math.min(playerMoney[i], thirstMoney);
                                            }
                                        } else if (gt.playerCongBai.size() == 1) {
                                            // nếu giết 1 nhà:
                                            if (i == CardConstant.FOURTH) { // thoát ngang trước khi có nhà bị cóng -> về bét
                                                betMoney += Math.min(playerMoney[i], fourthMoney);
                                            }
                                        }
                                    }
                                } else {    // về nhất ko giết
                                    // neu ko du thi con bao nhieu chung bay nhieu
                                    betMoney = Math.min(playerMoney[CardConstant.FOURTH], fourthMoney);
                                }
                                break;

                            case CardConstant.SECOND:
                                betMoney = 0;
                                if (gt.playerCongBai.size() >= 2) {
                                    // co 2 nha bi giet, ve nhi -> ve 3
                                    betMoney = (gt.playerCongBai.size() == 3) ? congMoney : thirstMoney;
                                    betMoney = Math.max(-betMoney, -playerMoney[CardConstant.SECOND]);
                                } else {
                                    // về nhì chỉ ăn nhì nếu về 3 không bị cóng (trong TH đó về 4 đã thoát ngang)
                                    if (!gt.playerCongBai.contains(gt.playerResult[CardConstant.THIRST])) {
                                        betMoney = Math.min(thirstMoney, playerMoney[CardConstant.THIRST]);
                                    }
                                }
                                break;

                            case CardConstant.THIRST:
                            case CardConstant.FOURTH:
                                if (gt.playerCongBai.contains(gt.playerResult[playerOrder])) {
                                    betMoney = congMoney;
                                } else {
                                    betMoney = (playerOrder == CardConstant.THIRST) ? thirstMoney : fourthMoney;
                                }
                                // neu ko du thi con bao nhieu chung bay nhieu
                                betMoney = Math.max(-betMoney, -playerMoney[playerOrder]);
                                break;
                        }
                        break;
                }

                if (gt.playerCongBai.isEmpty() || !gt.isTruTienBatCong
                        || (playerOrder != 0 && !gt.playerCongBai.contains(gt.playerResult[playerOrder]))) {
                    gt.playerBalance[playerOrder] = betMoney;
                    playerMoney[playerOrder] += betMoney;
                }

                // TNK: Kiem tra ket qua x2
//                String currPlayer = gt.playerResult[playerOrder];
//                if (gt.checkEffect(currPlayer, EffectType.X2_BET) && betMoney > 0) {
//                    gt.playerBalance[playerOrder] += betMoney;
//                    playerMoney[playerOrder] += betMoney;
//                }

            }

            /**
             * Bắt úng
             */
            gt.resetUngHangList();
//            if (gt.playerCongBai.size() > 0) {
            // hanv: xét úng hàng trong 2 trường hợp: bị cóng & về bét
            for (int k = 0; k < numOfJoinedPlayers; k++) {
                for (int j = 1; j < numOfJoinedPlayers; j++) {
                    if (gt.playerResult[j].equals(gt.getJoinedPlayerAt(k))) {
                        ungMoney = gameLogic.getMoneyUngDoChoi(gt, k);
                        if (ungMoney > 0) {
                            if (gt.playerCongBai.contains((gt.playerResult[j]))) {
                                if (!gt.isTruTienBatCong) {
                                    // neu bi cong thi chung cho nha ve nhat
                                    logGame.debug(gt.getJoinedPlayerAt(k) + " ung do choi, bi tru " + ungMoney);
                                    gt.playerBalance[j] -= Math.min(ungMoney, playerMoney[j]);
                                    gt.playerBalance[0] += Math.min(ungMoney, playerMoney[j]);
                                }
                            } else if (j == numOfJoinedPlayers - 1) {
                                // nếu ko bị cóng thì chỉ tính úng nhà về bét
                                gt.playerBalance[j] -= Math.min(ungMoney, playerMoney[j]);
                                if (!gt.playerThoatNgang.contains(gt.playerResult[j])) {
                                    // nếu nhà về bét ko thoát ngang thì cộng tiền cho nhà đánh úng
                                    gt.playerBalance[j - 1] += Math.min(ungMoney, playerMoney[j]);
                                }
                            }
                        }
                    }
                }
            }
//            }
//            else if (gt.playerCongBai.size() > 0 && numOfJoinedPlayers == 4) {
////                if (gt.playerCongBai.size() + gt.playerThoatNgang.size() >= 2) {
//                    for (int k = numOfJoinedPlayers - 1; k >= 0; k--) {
//                        for (int j = 3; j > 0; j--) {
//                            if (gt.playerResult[j].equals(gt.getJoinedPlayerAt(k))) {
//                                ungMoney = gameLogic.getMoneyUngDoChoi(gt, k);
//                                if (ungMoney > 0) {
//                                    // neu ko du thi con bao nhieu chung bay nhieu
//                                    gt.playerBalance[j] -= Math.min(ungMoney, playerMoney[j]);
//                                    gt.playerBalance[0] += Math.min(ungMoney, playerMoney[j]);
//                                    log.info(gt.getJoinedPlayerAt(k) + " ung do choi,bi phat " + ungMoney);
//                                }
//                            }
//                        }
//                    }
////                }
//            }
//            else {
//                //Ván bài bình thường,không có ai cóng bài,xét đồ chơi
//                // hanv: ko xét đối với user thoát ngang
//                for (int k = numOfJoinedPlayers - 1; k >= 0; k--) {
//                    if (!gt.playerResult[0].equals(gt.getJoinedPlayerAt(k))
//                            && !gt.playerThoatNgang.contains(gt.getJoinedPlayerAt(k))) {
//                        ungMoney = gameLogic.getMoneyUngDoChoi(gt, k);
//                        if (ungMoney > 0) {
//                            int position = 1;
//                            for (int j = gt.playerResult.length - 1; j > 0; j--) {
//                                if (gt.playerResult[j] == gt.getJoinedPlayerAt(k)) {
//                                    position = j;
//                                    break;
//                                }
//                            }
//                            /**
//                             * Người thoát ngang trả tiền úng cho người về nhất.
//                             * Người chơi hết ván trả tiền úng cho người về kế trước
//                             */
//
//                            // hanv bo luat thoat ngang
////                            if (gt.playerThoatNgang.contains(gt.getJoinedPlayerAt(k))) {
////                                gt.playerBalance[position] -= ungMoney;
////                                gt.playerBalance[0] += ungMoney;
////                            } else {
//                            // neu ko du thi con bao nhieu chung bay nhieu
//                            gt.playerBalance[position] -= Math.min(ungMoney, playerMoney[position]);
//                            gt.playerBalance[position-1] += Math.min(ungMoney, playerMoney[position]);
////                            }
//                            log.info(gt.getJoinedPlayerAt(k) + " ung do choi,bi phat " + ungMoney);
//                        }
//                    }
//                }
//            }
        }
        return isFinished;
    }

    private void sendChatMessage(IoSession session, String userName, Message clientMsg) {
        String tableID = SerializerHelper.readString(clientMsg);
        String sender = SerializerHelper.readString(clientMsg);
        String chatMsg = SerializerHelper.readString(clientMsg);

        if (chatMsg.startsWith("911 ") && (chatMsg.length() >= 5)) {
            int userType = (Integer) session.getAttribute("usertype");
            if (userType == 4) {
                session.setAttribute("cheat", Integer.parseInt(chatMsg.substring(4), 10));
                logGame.info("CHEAT enabled [{}] {}", userName, chatMsg);
                return;
            }
        }

        chatMsg = ServerService.filterChatMessage(chatMsg);

        GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableID);
        if (gt == null) {
            return;
        }
        if (gt.isPlaying) {
            // hanv: user nào ko có trong Joined Player hoặc có nhưng đã thoát ngang thì ko cho chat
            if (gt.playerJoined != null && !gt.playerJoined.contains(sender)) {
                logGame.info("Player {} not in Joined List chatting", userName);
                return;
            }
            if (gt.playerThoatNgang != null && gt.playerThoatNgang.contains(sender)) {
                logGame.info("Player {} in Exit List chatting", userName);
                return;
            }
        }
        broadcastTableMessage(tableID, sender, chatMsg);
        logChat.info("[{}] {}", userName, chatMsg);
    }

    public void broadcastSingleMessage(IoSession session, String tableID, String userName, String chatMsg) {

        Message msg = new Message(BeanConstant.BT_CHAT_BEAN, ServiceConstant.GAME_CARD_SERVICE);
        SerializerHelper.writeString(tableID, msg);
        SerializerHelper.writeString(userName, msg); // sender
        SerializerHelper.writeString(chatMsg, msg);
        session.write(msg);
    }

    public void broadcastTableMessage(String tableID, String sender, String chatMsg) {
        Message msg = new Message(BeanConstant.BT_CHAT_BEAN, ServiceConstant.GAME_CARD_SERVICE);

        SerializerHelper.writeString(tableID, msg);
        SerializerHelper.writeString(sender, msg);
        SerializerHelper.writeString(chatMsg, msg);

        ServerService.broadcastTable(tableID, msg, CardConstant.TIENLEN_TYPE);
    }

    private void autoFindRoom(IoSession session, int userID) {
        GameTable currentTable = null;
        try {
            String username = (String) session.getAttribute("user");
            String table = (String) session.getAttribute("table");
            if (table != null) {
                return;
            }

            VCoreUserDataProvider dataManager = new VCoreUserDataProvider();
            MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();
            int userMoney = commonDataManager.retrieveAccountBalance(userID);
            int i;
            Collection roomList = RoomManager.getInstance().getRoomList();
            //Tim ban da co nguoi choi va co so tien cuoc < 20% so tien hien co
            for (Iterator room = roomList.iterator(); room.hasNext();) {
                String roomCode = (String) room.next();
                //ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
//                String[] tableList = RoomManager.getInstance().getTableList();
//                int numOfTable = tableList.length;
                ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
                for (i = 0; i < arr.size(); i++) {
                    GameTable gt = (GameTable) arr.get(i);
                    if ((gt.getNumberOfPlayers() > 0) && gt.password == null
                            && (gt.getNumberOfPlayers() < gt.getMaxPlayers())
                            && (!gt.isPlaying) && (userMoney / 5 <= gt.betMoney && gt.betMoney <= userMoney)) {
                        if (!gt.playerList.contains(username)) {
                            gt.playerList.add(username);
                            gt.isPlayerChanged = true;
                        }
                        currentTable = gt;
                        session.setAttribute("table", gt.tableID);
                        log.info(gt.tableID);
                        break;
                    }
                }
                if (currentTable != null) {
                    break;
                }
            }

            //Tim ban da co nguoi choi va co so tien cuoc <= so tien hien co
            if (currentTable == null) {
                for (Iterator room = roomList.iterator(); room.hasNext();) {
                    String roomCode = (String) room.next();
                    ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
                    for (i = 0; i < arr.size(); i++) {
                        GameTable gt = (GameTable) arr.get(i);
                        if ((gt.getNumberOfPlayers() > 0) && gt.password == null
                                && (gt.getNumberOfPlayers() < gt.getMaxPlayers())
                                && (!gt.isPlaying) && (userMoney >= gt.betMoney)) {
                            if (!gt.playerList.contains(username)) {
                                gt.playerList.add(username);
                                gt.isPlayerChanged = true;
                            }
                            currentTable = gt;
                            session.setAttribute("table", gt.tableID);
                            log.info(gt.tableID);
                            break;
                        }
                    }
                    if (currentTable != null) {
                        break;
                    }
                }
            }

            //Tim ban co so tien cuoc <= 20% so tien hien co
            if (currentTable == null) {
                for (Iterator room = roomList.iterator(); room.hasNext();) {
                    String roomCode = (String) room.next();
                    ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
                    for (i = 0; i < arr.size(); i++) {
                        GameTable gt = (GameTable) arr.get(i);
                        if (gt.password == null && ((gt.getNumberOfPlayers() < gt.getMaxPlayers())
                                || ((gt.getNumberOfPlayers() == 0))) && (!gt.isPlaying)
                                && (userMoney / 5 <= gt.betMoney && gt.betMoney <= userMoney)) {
                            if (!gt.playerList.contains(username)) {
                                gt.playerList.add(username);
                                gt.isPlayerChanged = true;
                            }
                            currentTable = gt;
                            session.setAttribute("table", gt.tableID);
                            log.info(gt.tableID);
                            break;
                        }
                    }
                    if (currentTable != null) {
                        break;
                    }
                }
            }

            //Tim ban ko dieu kien
            if (currentTable == null) {
                for (Iterator room = roomList.iterator(); room.hasNext();) {
                    String roomCode = (String) room.next();
                    ArrayList arr = RoomManager.getInstance().getTableList(roomCode);
                    for (i = 0; i < arr.size(); i++) {
                        GameTable gt = (GameTable) arr.get(i);
                        if (gt.password == null && ((gt.getNumberOfPlayers() < gt.getMaxPlayers())
                                || ((gt.getNumberOfPlayers() == 0))) && (!gt.isPlaying)
                                && (userMoney >= gt.betMoney)) {
                            if (!gt.playerList.contains(username)) {
                                gt.playerList.add(username);
                                gt.isPlayerChanged = true;
                            }
                            currentTable = gt;
                            session.setAttribute("table", gt.tableID);
                            log.info(gt.tableID);
                            break;
                        }
                    }
                    if (currentTable != null) {
                        break;
                    }
                }
            }

            if (currentTable != null) {
                Message msg = new Message(BeanConstant.BT_RESPOND_JOIN_TABLE_BEAN, ServiceConstant.SYSTEM_SERVICE);
                SerializerHelper.writeBoolean(true, msg);
                SerializerHelper.writeString(currentTable.tableID, msg);
                SerializerHelper.writeLong(currentTable.betMoney, msg);
                SerializerHelper.writeInt(currentTable.getNumberOfPlayers(), msg);
                SerializerHelper.writeBoolean(currentTable.isPlaying, msg);
//                for (int j = currentTable.getNumberOfPlayers()-1; j>=0; j--) {
                int j = 0;
                while (j < currentTable.getNumberOfPlayers()) {
                    String userName = (String) currentTable.playerList.get(j);
                    SerializerHelper.writeString(userName, msg);

                    IoSession ses = PIServer.VMSProtocolHandler.getSessionByUserName(userName);
                    if (ses == null) {
                        // neu ko tim thay nguoi choi do mat ket noi --> kick
                        currentTable.playerList.remove(j);
                        continue;
                    }
                    int playerID = (Integer) ses.getAttribute("userid");
                    boolean isVIP = ses.getAttribute("vip") != null;

//                    UserObj userinfo = commonDataManager.getUserServiceInfoByUserID(playerID);
//                    int playerID = VCoreUserDataProvider.getInstance().GetUserIdByUsername(joinUser);
                    UserObj userinfo = dataManager.getAccountInfo(playerID);
                    SerializerHelper.writeLong(userinfo.money, msg);
                    SerializerHelper.writeInt(userinfo.avatar, msg);
                    SerializerHelper.writeInt(userinfo.nickColor, msg);
                    SerializerHelper.writeInt(userinfo.user_level, msg);          // joinUser level
                    SerializerHelper.writeBoolean(isVIP, msg);  // is Vip account
                    j++;
                }
                SerializerHelper.writeInt(currentTable.tableType, msg);
//                session.write(msg);
                ServerService.broadcastTable(currentTable.tableID, msg, CardConstant.TIENLEN_TYPE);

            }

            dataManager.closeConnection();
            commonDataManager.closeConnection();
        } catch (Exception ex) {
            log.error("autoFindRoom", ex);
            //ex.printStackTrace();
        }

    }

    public void finalizeChatChong(GameTable gt) {
        if (gt.soTienChat > 0) {
            int id = gt.playerJoined.indexOf(gt.nguoiBiChat);   // hanv add
            boolean conBai = false;
            for (int i = 0; i < 13; i++) {
                if (gt.cards[id][i] != 0) {
                    conBai = true;
                }
            }
            if (conBai) {                      // nguoi bi chat het bai thi ko tru tien
                int soTienChat = gt.soTienChat;
                int soTienThua = -gt.soTienChat;
//                if (gt.checkEffect(gt.nguoiChat, EffectType.X2_CHAT_HEO)) {
//                    soTienChat *= 2;
//                }
                int lostMoney = updateBalance(gt.nguoiBiChat, soTienThua,
                        String.format(StringConstant.strThuaChat, gt.nguoiBiChat, soTienThua), gt.game_id);
                int wonMoney = updateBalance(gt.nguoiChat, lostMoney,
                        String.format(StringConstant.strAnChat, gt.nguoiChat, lostMoney), gt.game_id);

                broadcastTableMessage(gt.tableID, gt.nguoiBiChat, "-" + lostMoney);
                broadcastTableMessage(gt.tableID, gt.nguoiChat, "+" + wonMoney);
            }
        }
        gt.soTienChat = 0;
        gt.nguoiBiChat = null;
        gt.nguoiChat = null;
        gt.chatChongList.clear();
    }

    // hanv: xác định lại danh sách úng hàng để gửi cho client vào cuối ván
    // chỉ gọi hàm này vào cuối ván
    public void finalizeUngList(GameTable gt) {

        int numOfJoinedPlayer = gt.getNumberOfJoinedPlayers();
        for (int i = 0; i < numOfJoinedPlayer; i++) {
            for (int j = 0; j < gt.ungHangList[i].length; j++) {
                gt.ungHangList[i][j] = 0;
            }
        }

        for (int i = numOfJoinedPlayer - 1; i > 0; i--) {
            if (i == numOfJoinedPlayer - 1 || gt.playerCongBai.contains(gt.playerResult[i])) {
                int j = gt.playerJoined.indexOf(gt.playerResult[i]);
                if (gameLogic.setPlayerUngList(gt, j)) {
                    if (gt.playerCongBai.contains(gt.playerResult[i])) {
                        // cóng -> chung cho nhà về nhất
                        for (int k = 0; k < gt.ungHangList[j].length; k++) {
                            int id = gt.playerJoined.indexOf(gt.playerResult[0]);   // chỉ số của nhà về nhất
                            gt.ungHangList[id][k] += gt.ungHangList[j][k];
                            gt.ungHangList[j][k] *= -1;
                        }
                    } else if (i == numOfJoinedPlayer - 1) {
                        for (int k = 0; k < gt.ungHangList[j].length; k++) {
                            if (!gt.playerThoatNgang.contains(gt.playerResult[i])) {
                                int id = gt.playerJoined.indexOf(gt.playerResult[i - 1]); // chỉ số của nhà đánh úng
                                if (id >= 0) {
                                    gt.ungHangList[id][k] += gt.ungHangList[j][k];
                                }
                            }
                            gt.ungHangList[j][k] *= -1;
                        }
                    }
                }
            }
        }
    }

    private void chargeFeeToUsers(GameTable gt) {
        if (gt.betMoney < CardConstant.MIN_BET_TO_FEE) {
            return;
        }

        VCoreUserDataProvider dataManager = new VCoreUserDataProvider();
        MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();

        try {
            for (int i = 0; i < gt.getNumberOfJoinedPlayers(); i++) {
                String userName = gt.getJoinedPlayerAt(i);
                IoSession session = PIServer.VMSProtocolHandler.getSessionByUserName(userName);
                // nguoi choi se mat tien neu roi he thong khi chua ket thuc van bai
                if (session == null) {
                    continue;
                }
                int userID = (Integer) session.getAttribute("userid");
                int balance = commonDataManager.retrieveAccountBalance(userID);

                int fee = Math.round(gt.betMoney * CardConstant.FEE_RATE_NORMAL_PER_GAME);
                if (session.getAttribute("vip") != null) {
                    fee = Math.round(gt.betMoney * CardConstant.FEE_RATE_VIP_PER_GAME);
                }
                if (fee > 0) {
                    balance -= fee;
                    commonDataManager.setUserMoney(userID, balance,
                            String.format(StringConstant.strSubtractFeeRate, fee, gt.betMoney), fee * -1, CardConstant.FEE_MONEY_TYPE, CardConstant.TIEN_LEN_SERVICE_ID, gt.game_id);

                    broadcastSingleMessage(session, gt.tableID, null, "@@FEE -" + fee);
                }
            }
        } catch (Exception ex) {
            log.error("chargeFeeToUsers", ex);
        } finally {
            dataManager.closeConnection();
            commonDataManager.closeConnection();
        }
    }

    public void updateClientCards(GameTable gt, String userName, byte userIndex) {

        logGame.warn("Update client cards for {}", userName);
        IoSession session = PIServer.VMSProtocolHandler.getSessionByUserName(userName);
        if (session == null) {
            return;
        }
        Message msg = new Message(BeanConstant.UPDATE_CLIENT_CARDS, ServiceConstant.GAME_CARD_SERVICE);

        SerializerHelper.writeString(gt.tableID, msg);
        SerializerHelper.writeString(userName, msg);
        SerializerHelper.writeBoolean(gt.isNewTurn, msg);

        byte cardNo = gt.noCardsLeft[userIndex];
        SerializerHelper.writeByte(cardNo, msg);
        logGame.debug("Cards: ", cardNo);

        String str = "";
        for (byte i = 0; i < 13; i++) {
            if (gt.cards[userIndex][i] == 0) {
                continue;
            }
            SerializerHelper.writeByte(gt.cards[userIndex][i], msg);
        }
        logGame.warn("Returned card: ", CardUtils.cardsToString(gt.cards[userIndex]));

        session.write(msg);
    }

    private void recordGameCards(GameTable gt) {
        MySQLCommonDataProvider commonDataManager = DbManager.GetCommonDataManager();
        try {
            int maxPlayers = gt.getMaxPlayers();
            String logFormatString = CardUtils.getLogGameString(maxPlayers);
            String logVarsString = "";
            int numPlayer = gt.getNumberOfJoinedPlayers();
            for (int i = 0; i < maxPlayers; i++) {
                String playerName = "null";
                String playerCard = "null";
                if (i < numPlayer) {
                    playerName = gt.getJoinedPlayerAt(i);
                    playerCard = CardUtils.cardsToString(gt.cards[i]);
                }
                logVarsString += "'" + playerName + "','" + playerCard + "',";
            }

            gt.game_id = commonDataManager.logGameTable(logFormatString, logVarsString, gt.betMoney, gt.getFirstPlayer(), CardConstant.TIEN_LEN_SERVICE_ID);
            gt.game_date = MySQLCommonDataProvider.logDateCurrent;
        } catch (Exception ex) {
            log.error("recordGameCards", ex);
        } finally {
            commonDataManager.closeConnection();
        }
    }

    private void sendUserAvailabe(IoSession session, int userID, Message clientMsg) {
        try {

            int GET_LIST_TYPE = SerializerHelper.readInt(clientMsg);
            int startIndex = SerializerHelper.readInt(clientMsg);
            Message msg = new Message(BeanConstant.RESPONSE_AVAILABLE_PLAYERS, ServiceConstant.GAME_CARD_SERVICE);
            ArrayList<UserObj> playerAvailable = new ArrayList<UserObj>();
            MySQLCommonDataProvider dataManager = DbManager.GetCommonDataManager();


            //Anlh Add : check memcache to except user playing phom
            ArrayList<String> PhomPlayer = new ArrayList<String>();
            if (Memcache.getInstance().get("PhomPlayer") != null) {
                PhomPlayer = (ArrayList<String>) Memcache.getInstance().get("PhomPlayer");
            }

            if (GET_LIST_TYPE == CardConstant.FREE_PLAYER_LIST) {
                // lấy danh sách người chơi đang rãnh
                playerAvailable.clear();
//                synchronized (PIServer.VMSProtocolHandler.clientSessions.values()) {
                    Iterator<IoSession> iter = PIServer.VMSProtocolHandler.clientSessions.values().iterator();
                    while (iter.hasNext()) {

                        IoSession userSession = (IoSession) iter.next();
                        if (userSession == null) {
                            continue;
                        }
                        String username = (String) userSession.getAttribute("user");
                        if (username == null) {
                            continue;
                        }
                        int userid = (Integer) userSession.getAttribute("userid");
                        boolean isPlayingPhom = false;
                        if (PhomPlayer != null) {
                            isPlayingPhom = PhomPlayer.contains(username);
                        }
                        if (userSession.getAttribute("table") == null && !isPlayingPhom) {
                            UserObj userInfo = dataManager.getUserServiceInfoByUserID(userid);
                            userInfo.username = username;
                            userInfo.userid = userid;
                            playerAvailable.add(userInfo);
                        }
                    }
//                }
            } else if (GET_LIST_TYPE == CardConstant.FREE_FRIEND_LIST) {
                // lấy danh sách bạn bè đang rãnh
                playerAvailable.clear();
                ArrayList<UserObj> friendList = dataManager.getFriendList(userID);
                for (int i = 0; i < friendList.size(); i++) {
                    UserObj player = (UserObj) friendList.get(i);
                    int userid = player.userid;
                    IoSession userSession = PIServer.VMSProtocolHandler.getSessionByUserID(userid);
                    if (userSession == null) {
                        continue;
                    }

                    boolean isPlayingPhom = false;
                    if (PhomPlayer != null) {
                        isPlayingPhom = PhomPlayer.contains(player.username);
                    }
                    if (userSession.getAttribute("table") == null && !isPlayingPhom) {
                        playerAvailable.add(player);
                    }
                }
            } else if (GET_LIST_TYPE == CardConstant.FREE_BEST_PLAYER_LIST) {
                // lấy danh sách cao thủ đang rãnh
                playerAvailable.clear();
                ArrayList<ArrayList> bestPlayerList = null;
                Object obj = Memcache.getInstance().get("bestPlayerList");
                if (obj != null) {
                    bestPlayerList = (ArrayList<ArrayList>) obj;
                }
//                if (bestPlayerList == null) {
//                    boolean updateResult = ResetTimeTask.updateBestPlayerList();
//                    if (!updateResult) {
//                        return;
//                    } else {
//                        bestPlayerList = (ArrayList<ArrayList>) Memcache.getInstance().get("bestPlayerList");
//                    }
//                }


                for (int i = 0; i < bestPlayerList.size(); i++) {
                    ArrayList player = (ArrayList) bestPlayerList.get(i);
                    int userid = (Integer) player.get(0);
                    String userName = (String) player.get(1);
                    IoSession userSession = PIServer.VMSProtocolHandler.getSessionByUserID(userid);
                    if (userSession == null) {
                        continue;
                    }
                    boolean isPlayingPhom = false;
                    if (PhomPlayer != null) {
                        isPlayingPhom = PhomPlayer.contains(userName);
                    }
                    if (userSession.getAttribute("table") == null && !isPlayingPhom) {
                        UserObj userInfo = dataManager.getUserServiceInfoByUserID(userid);
                        userInfo.username = userName;
                        userInfo.userid = userid;
                        playerAvailable.add(userInfo);
                    }
                }
            }


            //gửi sanh sách theo trang :
            int numberOfUser = playerAvailable.size();
            if (numberOfUser == 0) {
                SerializerHelper.writeInt(numberOfUser, msg);
                session.write(msg);
                return;
            }
            //list có số người chơi ko đủ 5
            if (numberOfUser < CardConstant.NUMBER_PLAYERS_PER_PAGE) {
                SerializerHelper.writeInt(numberOfUser, msg);
                for (int i = 0; i < numberOfUser; i++) {
                    UserObj userObj = (UserObj) playerAvailable.get(i);
                    if (userObj == null) {
                        continue;
                    }
                    SerializerHelper.writeInt(userObj.userid, msg);
                    SerializerHelper.writeString(userObj.username, msg);
                    SerializerHelper.writeInt(userObj.money, msg);
                    SerializerHelper.writeInt(userObj.avatar, msg);
                    SerializerHelper.writeInt(userObj.nickColor, msg);
                    SerializerHelper.writeString(userObj.emotion_status, msg);
                    SerializerHelper.writeInt(userObj.user_level, msg);
                }
                SerializerHelper.writeInt(0, msg);
            } else {
                //list có 20 người chơi trở lên
                if (startIndex > numberOfUser) {
                    startIndex = 0;
                }
                int endIndex = startIndex + CardConstant.NUMBER_PLAYERS_PER_PAGE;
                int bonusIndex = 0;
                if (endIndex > numberOfUser) {
                    bonusIndex = endIndex - numberOfUser;

                    endIndex = numberOfUser;
                }
                if (bonusIndex > numberOfUser) {
                    bonusIndex = 0;
                }
                SerializerHelper.writeInt(CardConstant.NUMBER_PLAYERS_PER_PAGE, msg);

                for (int i = startIndex; i < endIndex; i++) {
                    UserObj userObj = (UserObj) playerAvailable.get(i);
                    if (userObj == null) {
                        continue;
                    }
                    SerializerHelper.writeInt(userObj.userid, msg);
                    SerializerHelper.writeString(userObj.username, msg);
                    SerializerHelper.writeInt(userObj.money, msg);
                    SerializerHelper.writeInt(userObj.avatar, msg);
                    SerializerHelper.writeInt(userObj.nickColor, msg);
                    SerializerHelper.writeString(userObj.emotion_status, msg);
                    SerializerHelper.writeInt(userObj.user_level, msg);
                }
                //cuộn trang cuối về đầu nếu kết thúc
                if (endIndex == numberOfUser) {
                    for (int i = 0; i < bonusIndex; i++) {
                        UserObj userObj = (UserObj) playerAvailable.get(i);
                        if (userObj == null) {
                            continue;
                        }
                        SerializerHelper.writeInt(userObj.userid, msg);
                        SerializerHelper.writeString(userObj.username, msg);
                        SerializerHelper.writeInt(userObj.money, msg);
                        SerializerHelper.writeInt(userObj.avatar, msg);
                        SerializerHelper.writeInt(userObj.nickColor, msg);
                        SerializerHelper.writeString(userObj.emotion_status, msg);
                        SerializerHelper.writeInt(userObj.user_level, msg);
                    }
                    SerializerHelper.writeInt(bonusIndex, msg);
                    //log.info("bonus :   " + bonusIndex);
                } else {
                    SerializerHelper.writeInt(endIndex, msg);
                    //log.info("end index 2 :   " + endIndex);
                }
            }
            session.write(msg);

        } catch (Exception ex) {
            log.error("sendUserAvailabe", ex);
            ex.printStackTrace();
        }
    }
    private static int INVITE_FAILED = 0;
    private static int INVITE_SUCCESS = 1;
    private static int BEING_INVITED = 2;

    private void inviteUser(IoSession session, String userName, Message clientMsg) {
        try {
            String tableId = SerializerHelper.readString(clientMsg);
            int userInvitedId = SerializerHelper.readInt(clientMsg);
            if (tableId == null || userInvitedId < 0) {
                return;
            }
            GameTable gt = gt = RoomManager.getInstance().getTable(tableId);
            if (gt == null) {
                return;
            }

            //tín hiệu gừi về client :
            //0 : mời thất bại
            //1 : mời thành công, chờ trả lời
            //2 : bạn nhận được lời mời
            Message msg = new Message(BeanConstant.RESPONSE_INVITE_PLAYER, ServiceConstant.GAME_CARD_SERVICE);
            IoSession inviteUserSession = PIServer.VMSProtocolHandler.getSessionByUserID(userInvitedId);
            if (inviteUserSession == null || inviteUserSession.getAttribute("user") == null) {
                SerializerHelper.writeInt(INVITE_FAILED, msg);
                session.write(msg);
                return;
            }

            ArrayList<String> PhomPlayer = null;
            if (Memcache.getInstance().get("PhomPlayer") != null) {
                PhomPlayer = (ArrayList<String>) Memcache.getInstance().get("PhomPlayer");
            }
            String userInviteName = (String) inviteUserSession.getAttribute("user");
            boolean isPlayingPhom = false;
            if (PhomPlayer != null) {
                isPlayingPhom = PhomPlayer.contains(userInviteName);
            }


            if (inviteUserSession.getAttribute("table") == null && !isPlayingPhom) {
                Message msgInvite = new Message(BeanConstant.RESPONSE_INVITE_PLAYER, ServiceConstant.GAME_CARD_SERVICE);
                SerializerHelper.writeInt(INVITE_SUCCESS, msgInvite);
                session.write(msgInvite);

                SerializerHelper.writeInt(BEING_INVITED, msg);
                SerializerHelper.writeString(tableId, msg);
                SerializerHelper.writeString(userName, msg);
                inviteUserSession.write(msg);
            } else {
                SerializerHelper.writeInt(INVITE_FAILED, msg);
                session.write(msg);
            }
        } catch (Exception ex) {
            log.error("inviteUser", ex);
        }
    }

    private void lockTable(IoSession session, String userName, int userID, Message clientMsg) {

        try {
            String tableId = SerializerHelper.readString(clientMsg);
            String password = SerializerHelper.readString(clientMsg);
            password = Base64.toString(password);
            GameTable gt = (GameTable) RoomManager.getInstance().getTable(tableId);
            boolean isVIP = session.getAttribute("vip") != null;

            if ((gt == null) || (gt.isPlaying) || (!isVIP) || (gt.playerList.indexOf(userName) != 0)) {
                log.debug("Invalid table or not table master or not VIP {}, {}", tableId, userName);
                return;
            }

            byte lockResult = 1;
            String lockMsg = null;
            if (password != null && password.length() > 0) {
                lockMsg = StringConstant.strTableLocked;
                boolean isValidPW = (password.length() >= 3 && password.length() <= 6);
                if (isValidPW) {
                    for (int i = 0; i < password.length(); i++) {
                        char c = password.charAt(i);
                        if ((c < '0') || ('9' < c && c < 'A') || ('Z' < c && c < 'a') || ('z' < c)) {
                            isValidPW = false;
                            break;
                        }
                    }
                }
                if (isValidPW) {
                    lockMsg = StringConstant.strTableLocked;
                    gt.password = password;
                } else {
                    log.debug("Invalid password {}", password);
                    lockResult = -1;
                    lockMsg = StringConstant.strTablePWError;
                }
            } else {
                lockMsg = StringConstant.strTableUnlocked;
                gt.password = null;
            }

            Message msg = new Message(BeanConstant.RESPONSE_LOCK_TABLE, ServiceConstant.GAME_CARD_SERVICE);
            SerializerHelper.writeByte(lockResult, msg);
            SerializerHelper.writeString(lockMsg, msg);
            session.write(msg);
        } catch (Exception e) {
            log.error("Error Lock table: ", e);
        }
    }
}
